Example #1
0
class MonteCarlo(object):
    """The base class for the MonteCarlo-classes"""
    def __init__(self, custom_parameters_path, project_parameters_path,
                 custom_analysis):
        """
        constructor of the MonteCarlo-Object
        Keyword arguments:
        self                    : an instance of the class
        custom_parameters_path  : path of the Monte Carlo simulation
        project_parameters_path : path of the project parameters file
        custom_analysis         : analysis stage of the problem
        """

        # analysis: analysis stage of the current problem
        if (custom_analysis is not None):
            self.SetAnalysis(custom_analysis)
        else:
            raise Exception(
                "Please provide a Kratos specific application analysis stage for the current problem"
            )
        # project_parameters_path: path to the project parameters json file
        if (project_parameters_path is not None):
            self.project_parameters_path = project_parameters_path
        else:
            raise Exception(
                "Please provide the path of the project parameters json file")
        # default settings of the Monte Carlo algorithm
        # run_monte_carlo           : boolean setting if run or not the algorithm
        # tolerance                 :  relative tolerance
        # tolerance_absolute        : safety tolerance (absolute). Useful if expected value (qoi) is zero
        # confidence                : confidence on tolerance
        # batch_size                : number of samples per batch size
        # initial_number_batches    : the starting number of batches
        # maximum_number_iterations : maximum number of iterations to run
        # convergence_criteria      : convergence criteria to compute convergence
        default_settings = KratosMultiphysics.Parameters("""
        {
            "run_monte_carlo" : true,
            "tolerance"  : 1e-1,
            "tolerance_absolute" : 1e-6,
            "confidence" : 9e-1,
            "batch_size" : 25,
            "initial_number_batches" : 1,
            "maximum_number_iterations": 10,
            "convergence_criteria" : "MC_sample_variance_sequential_stopping_rule"
        }
        """)
        # set XMC parameters
        self.custom_parameters_path = custom_parameters_path
        self.SetXMCParameters()
        # validate and assign default parameters
        self.settings.ValidateAndAssignDefaults(default_settings)
        # convergence: boolean variable defining if MC algorithm has converged
        self.convergence = False
        # handle confidence = 1.0
        if (self.settings["confidence"].GetDouble() == 1.0):
            self.settings["confidence"].SetDouble(
                0.999
            )  # reduce confidence to not get +inf for cphi_confidence (coefficient used in convergence criterias)
        # set error probability = 1.0 - confidence on given tolerance
        self.settings.AddEmptyValue("error_probability")
        self.settings["error_probability"].SetDouble(
            1.0 - self.settings["confidence"].GetDouble())
        # current_number_levels: number of levels of MC by default = 0 (we only have level 0)
        self.current_number_levels = 0
        # current_level: current level of work, current_level = 0 for MC
        self.current_level = 0
        # theta_i: splitting parameter \in (0,1), this affects bias and statistical error in the computation of the total error
        self.theta_i = None
        # TErr: total error of MC algorithm, the sum of bias and statistical error is an overestmation of the real total error
        #       TErr := \abs(E^MC[QoI] - E[QoI])"""
        self.TErr = None
        # QoI: Quantity of Interest of the considered problem
        self.QoI = StatisticalVariable()
        # initialize all the variables of the StatisticalVariable class: MC has only one level, i.e. level 0
        self.QoI.InitializeLists(
            self.current_number_levels + 1,
            self.settings["initial_number_batches"].GetInt())
        # batches_number_samples: total number of samples, organized in level and batches
        # batches_number_samples = [ [ [level0_batch1] [level1_batch1] .. ] [ [level0_batch2] [level1_batch2] ..] .. ]
        # for MC: batches_number_samples = [ [ level0_batch1 ] [ level0_batch2 ] [ level0_batch3 ] .. ]
        self.batches_number_samples = []
        # number_samples: total number of samples used for the computation of global power sums
        # number_samples = [total_level0 total_level1 ..]
        self.number_samples = []
        # running_number_samples: total number of samples running
        self.running_number_samples = []
        # batches_launched: boolean true or false if batch launched or not
        self.batches_launched = []
        # batches_execution_finished: boolean true or false if batch finished or not
        self.batches_execution_finished = []
        # batches_analysis_finished: boolean true or false if statistical analysis of batch is finished or not
        self.batches_analysis_finished = []
        # batches_convergence_finished: boolean true or false if convergence computation of batch is finished or not
        self.batches_convergence_finished = []
        # batch_size: number of iterations of each epoch
        self.batch_size = []
        # current_convergence_batch: current batch for which convergence is computed
        self.current_convergence_batch = None
        # iteration counter
        self.iteration_counter = 0
        # set convergence criteria
        self.SetConvergenceCriteria()

        # pickled_model: serialization of model Kratos object of the problem
        self.pickled_model = None
        # pickled_project_parameters: serialization of project parameters Kratos object of the problem
        self.pickled_project_parameters = None
        # construct the pickled model and pickled project parameters of the problem
        self.is_project_parameters_pickled = False
        self.is_model_pickled = False

    """
    function executing the Monte Carlo algorithm
    input: self : an instance of the class
    """

    def Run(self):
        if (self.settings["run_monte_carlo"].GetBool()):
            self.SerializeModelParameters()
            self.InitializeMCPhase()
            self.ScreeningInfoInitializeMCPhase()
            self.LaunchEpoch()
            self.FinalizeMCPhase()
            self.ScreeningInfoFinalizeMCPhase()
            while (self.convergence is not True):
                self.InitializeMCPhase()
                self.ScreeningInfoInitializeMCPhase()
                self.LaunchEpoch()
                self.FinalizeMCPhase()
                self.ScreeningInfoFinalizeMCPhase()
                #self.convergence = True
        else:
            print("\n", "#" * 50, "Not running Monte Carlo algorithm",
                  "#" * 50)
            pass

    """
    function running one Monte Carlo epoch
    input: self : an instance of the class
    """

    def LaunchEpoch(self):
        for batch in range(len(self.batches_number_samples)):
            if (self.batches_launched[batch] is not True):
                self.batches_launched[batch] = True
                batch_results = []
                for level in range(self.current_number_levels + 1):
                    for instance in range(
                            self.batches_number_samples[batch][level]):
                        self.running_number_samples[
                            level] = self.running_number_samples[level] + 1
                        batch_results.append(self.ExecuteInstance())
                        self.running_number_samples[
                            level] = self.running_number_samples[level] - 1
                self.AddResults(batch_results, batch)
                self.batches_execution_finished[batch] = True

    """
    function executing an instance of the Monte Carlo algorithm
    requires:  self.pickled_model              : pickled model
               self.pickled_project_parameters : pickled parameters
               self.current_analysis_stage     : analysis stage of the problem
    input:  self: an instance of the class
    output: MonteCarloResults class : an instance og the MonteCarloResults class
            current_level           : level of the current MC simulation (= 0)
    """

    def ExecuteInstance(self):
        # ensure working level is level 0
        current_level = self.current_level
        sample = generator.GenerateSample(self.problem_name)
        if (current_level != 0):
            raise Exception(
                "current work level must be = 0 in the Monte Carlo algorithm")
        return (ExecuteInstanceAux_Task(self.pickled_model,
                                        self.pickled_project_parameters,
                                        sample, self.GetAnalysis(),
                                        self.current_level), current_level)
        # return (ExecuteInstanceAux_Task(self.serialized_model,self.serialized_project_parameters,sample,self.GetAnalysis(),self.current_level),current_level)

    """
    function initializing the MC phase
    input:  self : an instance of the class
    """

    def InitializeMCPhase(self):
        current_level = self.current_level
        if (current_level != 0):
            raise Exception(
                "current work level must be = 0 in the Monte Carlo algorithm")
        # update iteration counter
        self.iteration_counter = self.iteration_counter + 1
        # update number of samples (MonteCarlo.batches_number_samples) and batch size
        if (self.iteration_counter == 1):
            self.batch_size = [
                self.settings["batch_size"].GetInt()
                for _ in range(self.current_number_levels + 1)
            ]
            self.batches_number_samples = [[
                self.settings["batch_size"].GetInt()
                for _ in range(self.current_number_levels + 1)
            ] for _ in range(self.settings["initial_number_batches"].GetInt())]
            self.number_samples = [
                0 for _ in range(self.current_number_levels + 1)
            ]
            self.running_number_samples = [
                0 for _ in range(self.current_number_levels + 1)
            ]
            self.batches_launched = [
                False for _ in range(
                    self.settings["initial_number_batches"].GetInt())
            ]
            self.batches_execution_finished = [
                False for _ in range(
                    self.settings["initial_number_batches"].GetInt())
            ]
            self.batches_analysis_finished = [
                False for _ in range(
                    self.settings["initial_number_batches"].GetInt())
            ]
            self.batches_convergence_finished = [
                False for _ in range(
                    self.settings["initial_number_batches"].GetInt())
            ]
        elif (self.iteration_counter > 1):
            # add new batches in case convergence = False
            if (self.convergence is not True):
                # estimate batches to append and batch size
                self.UpdateBatches()
            for batch in range(len(self.batches_launched)):
                if (self.batches_launched[batch] is False):
                    self.batches_number_samples.append(self.batch_size)
                    # append execution, analysis and convergence False booleans
                    self.batches_execution_finished.append(False)
                    self.batches_analysis_finished.append(False)
                    self.batches_convergence_finished.append(False)
                    # append new batch lists to self.QoI.values and self.QoI.power_sum_batches_*
                    self.QoI.values.append(
                        [[] for _ in range(self.current_number_levels + 1)])
                    self.QoI.power_sum_batches_1.append(
                        [[] for _ in range(self.current_number_levels + 1)])
                    self.QoI.power_sum_batches_2.append(
                        [[] for _ in range(self.current_number_levels + 1)])
                    self.QoI.power_sum_batches_3.append(
                        [[] for _ in range(self.current_number_levels + 1)])
                    self.QoI.power_sum_batches_4.append(
                        [[] for _ in range(self.current_number_levels + 1)])
                    self.QoI.batches_number_samples.append(
                        [0 for _ in range(self.current_number_levels + 1)])
        else:
            pass

    """
    function updating number of batches and batch size
    input:  self : an instance of the class
    """

    def UpdateBatches(self):
        # set here number of batches to append
        if (len(self.batches_number_samples) >=
                self.settings["maximum_number_iterations"].GetInt()):
            new_number_batches = 0
        else:
            new_number_batches = 1
        # update batch size
        self.UpdateBatchSize()
        for _ in range(new_number_batches):
            self.batches_launched.append(False)

    """
    function updating batch size
    input:  self : an instance of the class
    TODO: for now batch_size = batch_size, in future flags can be added to have different behaviours
    """

    def UpdateBatchSize(self):
        self.batch_size = copy.copy(self.batch_size)

    """
    function finalizing the MC phase
    input:  self : an instance of the class
    """

    def FinalizeMCPhase(self):
        current_level = self.current_level
        if (current_level != 0):
            raise Exception(
                "current work level must be = 0 in the Monte Carlo algorithm")
        # update power sums batches
        for batch in range(len(
                self.batches_number_samples)):  # i.e. total number of batches
            if (self.batches_execution_finished[batch] is True
                    and self.batches_analysis_finished[batch] is not True
                ):  # consider batches completed and not already analysed
                self.QoI.UpdateBatchesPassPowerSum(current_level, batch)
                self.batches_analysis_finished[batch] = True
        continue_iterating = True
        for batch in range(len(self.batches_number_samples)):
            if (self.batches_execution_finished[batch] is True
                    and self.batches_analysis_finished[batch] is True
                    and self.batches_convergence_finished[batch] is not True
                    and continue_iterating
                ):  # consider batches completed, analysed and
                # for which convergence has not been computed
                continue_iterating = False
                # update working convergence batch
                self.current_convergence_batch = batch
                # update global power sums from batches power sums
                self.QoI.UpdateGlobalPowerSums(current_level, batch)
                # update number of samples used to compute global power sums
                for level in range(self.current_level + 1):
                    self.number_samples[level] = self.number_samples[
                        level] + self.batches_number_samples[batch][level]
                # compute the central moments we can't derive from the unbiased h statistics
                # compute from scratch the absolute central moment because we can't retrieve it from the power sums
                if (self.convergence_criteria ==
                        "MC_higher_moments_sequential_stopping_rule"):
                    self.QoI.central_moment_from_scratch_3_absolute_to_compute = True
                    self.QoI.ComputeSampleCentralMomentsFromScratch(
                        current_level, self.number_samples[current_level]
                    )  # not possible to use self.StatisticalVariable.number_samples[current_level]
                    # inside the function because it is a pycompss.runtime.binding.Future object
                self.QoI.ComputeHStatistics(current_level)
                # self.QoI.ComputeSkewnessKurtosis(current_level)
                self.CheckConvergence(current_level)
                self.batches_convergence_finished[batch] = True
                # synchronization point needed to launch new tasks if convergence is false
                # put the synchronization point as in the end as possible
                self.convergence = get_value_from_remote(self.convergence)
                # bring to master what is needed to print
                self.QoI.h_statistics_1 = get_value_from_remote(
                    self.QoI.h_statistics_1)
                self.QoI.h_statistics_2 = get_value_from_remote(
                    self.QoI.h_statistics_2)
                break  # break the for loop after the convergence of the first available batch is computed
        if (self.iteration_counter >=
                self.settings["maximum_number_iterations"].GetInt()):
            self.convergence = True

    """
    function adding QoI values to the corresponding level
    input:  self               : an instance of the class
            simulation_results : tuple=(instance of MonteCarloResults class, working level)
            batch_number       : number of working batch
            mini_batch_size    : compute add result grouping results with this size
    """

    def AddResults(self, simulation_results, batch_number, mini_batch_size=50):
        current_level = simulation_results[0][
            1]  # not compss future object, it is working level
        if (current_level != 0):
            raise Exception(
                "current work level must be = 0 in the Monte Carlo algorithm")
        simulation_results = list(map(lambda x: x[0], simulation_results))
        number_samples_batches_level = 0
        while (len(simulation_results) >= 1):
            new_simulations = simulation_results[mini_batch_size:]
            current_simulations = simulation_results[:mini_batch_size]
            number_samples_batches_level += len(current_simulations)
            self.QoI.values[batch_number][current_level].append(
                AddResultsAux_Task(current_level, *current_simulations))
            simulation_results = new_simulations
        self.QoI.batches_number_samples[batch_number][
            current_level] = number_samples_batches_level

    """
    function serializing and pickling the model and the project parameters of the problem
    the serialization-pickling process is the following:
    i)   from Model/Parameters Kratos object to StreamSerializer Kratos object
    ii)  from StreamSerializer Kratos object to pickle string
    iii) from pickle string to StreamSerializer Kratos object
    iv)  from StreamSerializer Kratos object to Model/Parameters Kratos object
    requires: self.project_parameters_path: path of the Project Parameters file
    builds: self.pickled_model              : pickled model
            self.pickled_project_parameters : pickled project parameters
    input:  self : an instance of the class
    """

    def SerializeModelParameters(self):
        with open(self.project_parameters_path, 'r') as parameter_file:
            parameters = KratosMultiphysics.Parameters(parameter_file.read())
        # create wrapper instance to modify current project parameters
        self.wrapper = ParametersWrapper(parameters)
        # save problem name
        self.problem_name = parameters["problem_data"][
            "problem_name"].GetString()
        # serialize parmeters (to avoid adding new data dependent on the application)
        parameters = self.wrapper.SetModelImportSettingsInputType(
            "use_input_model_part")
        serialized_project_parameters = KratosMultiphysics.StreamSerializer()
        serialized_project_parameters.Save("ParametersSerialization",
                                           parameters)
        self.serialized_project_parameters = serialized_project_parameters
        # reset to read the model part
        parameters = self.wrapper.SetModelImportSettingsInputType("mdpa")
        # prepare the model to serialize
        model = KratosMultiphysics.Model()
        fake_sample = generator.GenerateSample(
            self.problem_name)  # only used to serialize
        simulation = self.analysis(model, parameters, fake_sample)
        simulation.Initialize()
        # reset general flags
        main_model_part_name = self.wrapper.GetModelPartName()
        simulation.model.GetModelPart(
            main_model_part_name).ProcessInfo.SetValue(
                KratosMultiphysics.IS_RESTARTED, True)
        # serialize model
        serialized_model = KratosMultiphysics.StreamSerializer()
        serialized_model.Save("ModelSerialization", simulation.model)
        self.serialized_model = serialized_model
        # pickle model and parameters
        pickled_model = pickle.dumps(
            serialized_model, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        pickled_project_parameters = pickle.dumps(
            serialized_project_parameters, 2)
        self.pickled_model = pickled_model
        self.pickled_project_parameters = pickled_project_parameters
        self.is_project_parameters_pickled = True
        self.is_model_pickled = True
        print("\n", "#" * 50, " SERIALIZATION COMPLETED ", "#" * 50, "\n")

    """
    function reading the xmc parameters passed from json file
    input:  self : an instance of the class
    """

    def SetXMCParameters(self):
        with open(self.custom_parameters_path, 'r') as parameter_file:
            parameters = KratosMultiphysics.Parameters(parameter_file.read())
        self.settings = parameters["monte_carlo"]

    """
    function defining the Kratos specific application analysis stage of the problem
    input:  self                       : an instance of the class
            application_analysis_stage : working analysis stage Kratos class
    """

    def SetAnalysis(self, application_analysis_stage):
        self.analysis = application_analysis_stage

    """
    function returning the Kratos specific application analysis stage of the problem previously defined
    input:  self          : an instance of the class
    output: self.analysis : working analysis stage Kratos class
    """

    def GetAnalysis(self):
        if (self.analysis is not None):
            return self.analysis
        else:
            print(
                "Provide a Kratos specific application analysis stage for the current problem."
            )

    """
    function checking the convergence of the MC algorithm, with respect to the selected convergence criteria
    input:  self  : an instance of the class
            level : working level
    """

    def CheckConvergence(self, level):
        current_number_samples = self.QoI.number_samples[level]
        current_mean = self.QoI.h_statistics_1[level]
        current_h2 = self.QoI.h_statistics_2[level]
        current_h3 = self.QoI.h_statistics_3[level]
        current_sample_central_moment_3_absolute = self.QoI.central_moment_from_scratch_3_absolute[
            level]
        current_h4 = self.QoI.h_statistics_4[level]
        current_tol = self.settings["tolerance"].GetDouble()
        current_tol_absolute = self.settings["tolerance_absolute"].GetDouble()
        current_error_probability = self.settings["error_probability"].GetDouble(
        )  # the "delta" in [3] in the convergence criteria is the error probability
        convergence_criteria = self.convergence_criteria
        convergence_boolean = CheckConvergenceAux_Task(current_number_samples,current_mean,current_h2,\
            current_h3,current_sample_central_moment_3_absolute,current_h4,current_tol,current_tol_absolute,current_error_probability,convergence_criteria)
        self.convergence = convergence_boolean

    """
    function printing informations about initializing MLMC phase
    input:  self : an instance of the class
    """

    def ScreeningInfoInitializeMCPhase(self):
        print("\n", "#" * 50, " MC iter =  ", self.iteration_counter, "#" * 50,
              "\n")

    """
    function printing informations about finalizing MC phase
    input:  self : an instance of the class
    """

    def ScreeningInfoFinalizeMCPhase(self):
        print("current convergence batch =", self.current_convergence_batch)
        # print("values computed of QoI = ",self.QoI.values)
        print("current batches", self.batches_number_samples)
        print("check number samples of batch statistical variable class",
              self.QoI.batches_number_samples)
        print("current number of samples = ", self.number_samples)
        print("monte carlo mean and variance QoI estimators = ",
              self.QoI.h_statistics_1, self.QoI.h_statistics_2)
        print("convergence = ", self.convergence)

    """
    function setting the convergence criteria the algorithm will exploit
    input:  self : an instance of the class
    """

    def SetConvergenceCriteria(self):
        convergence_criteria = self.settings["convergence_criteria"].GetString(
        )
        if (convergence_criteria !=
                "MC_sample_variance_sequential_stopping_rule"
                and convergence_criteria !=
                "MC_higher_moments_sequential_stopping_rule"
                and convergence_criteria != "total_error_stopping_rule" and
                convergence_criteria != "relative_total_error_stopping_rule"):
            raise Exception(
                "The selected convergence criteria is not yet implemented, plese select one of the following: \n i)  MC_sample_variance_sequential_stopping_rule \n ii) MC_higher_moments_sequential_stopping_rule"
            )
        self.convergence_criteria = convergence_criteria
Example #2
0
class KratosSolverWrapper(sw.SolverWrapper):
    """
    Solver wrapper class managing Kratos Multiphysics (Kratos) solver.

    Attributes:
    - analysis: Kratos analysis stage. The analysis stage class default name is SimulationScenario. The default file name is simulation_scenario.
    - adaptive_refinement_jump_to_finest_level: boolean. Used in multilevel algorithms when "stochastic_adaptive_refinement" strategy is selected. If true, intermediate refinement indices are skipped. Set by adaptiveRefinementJumpToFinestLevel key.
    - asynchronous: boolean. If true, the asynchronous algorithm should be run. If false, the standard synchronous algorithms should be run. Set by asynchronous key.
    - different_tasks: boolean. Used in multilevel algorithms when "stochastic_adaptive_refinement" strategy is selected. If true, different indices are run all together in the same task. If false, each index is run in a different task. Set by not TaskAllAtOnce key.
    - fake_sample_to_serialize: list. A variable which is used just to serialize the Kratos Model and the Kratos Parameters. The list should be of the same type of the random variable generated by the generator. Set by fakeRandomVariable key.
    - mapping_output_quantities: boolean. If true, the analysis stage is prepared to map the variables of interest to a reference Kratos Model. By default, such Kratos Model is the coarsest index. Set by mappingOutputQuantities key.
    - number_contributions_per_instance: integer. Defines the number of realization per each solve call. Useful if one wants to exploit ensemble average, together with hierarchical Monte Carlo methods. Set by numberContributionsPerInstance key.
    - number_qoi: integer. Defines the number of Quantities of Interest the user wants to return. Set by numberQoI key.
    - number_combined_qoi: integer. Defines the number of combined Quantities of Interest the user wants to return. Set by numberCombinedQoi key.
    - number_multi_qoi: integer. Defines the number of vector Quantities of Interest the user wants to return. Set by numberMultiQoI key.
    - number_multi_combined_qoi: integer. Defines the number of vector combined Quantities of Interest the user wants to return. Set by numberMultiCombinedQoi key.
    - outputBatchSize: integer. Defines the size of each sub-list of the Quantities of Interest list which is returned by the solve method. It is alternative to outputDimension, defined below. Set by OutputBatchSize.
    - outputDimension: integer or list of integers. If integer, equals to len(sample), where sample is the first output argument of self.solve(). If list of integers, then it means that samples are split in future lists, and outputDimension is [len(subSample) for subSample in sample]. Set by OutputDimension key.
    - print_to_file: boolean. If true, prepares the distributed environment programing model PyCOMPSs to write a file inside the solve task. Set by printToFile key.
    - project_parameters_path: string or list of strings. Defines the path to Kratos Project Parameters. Set by projectParametersPath key.
    - refinement_parameters_path: string. Define the path to the Kratos Adaptive Refinement Project Parameters. Set by refinementParametersPath key.
    - refinement_strategy: string. Options are: "reading_from_file", "deterministic_adaptive_refinement", "stochastic_adaptive_refinement". It defines the refinement strategy for multilevel algorithms. Set by refinementStrategy key.
    - size_multi_x_qoi: integer or list of integers. Defines the size of each vector quantity if interest. If integer, vector quantities of interest have the same size. If list, the list has the same length of numberMultiQoI+numberMultiCombinedQoI. It is required to set a priori this value only because of returnZeroQoiAndTime_Task, which needs to know how many 0s to return. Set by sizeMultiXQoI.

    Methods:
    - serialize: method serializing Kratos Model and Kratos Parameters.
    - solve: method running the problem.
    Other methods are called from the two methods defined above.
    """

    # TODO: solverWrapperIndex will be removed from here and will have an indicator about the level we are at and not which algorithm we are using
    # TODO: are both outputBatchSize and outputBatchSize needed? Probably not.
    # TODO: integrate MultiXMomentEstimators with ensemble average.
    def __init__(self, **keywordArgs):
        super().__init__(**keywordArgs)
        self.analysis = SimulationScenario
        self.adaptive_refinement_jump_to_finest_level = keywordArgs.get(
            "adaptiveRefinementJumpToFinestLevel", False)
        self.asynchronous = keywordArgs.get("asynchronous", False)
        self.different_tasks = not keywordArgs.get('taskAllAtOnce', False)
        self.fake_sample_to_serialize = keywordArgs.get('fakeRandomVariable')
        self.mapping_output_quantities = keywordArgs.get(
            "mappingOutputQuantities", False)
        self.number_contributions_per_instance = keywordArgs.get(
            "numberContributionsPerInstance", 1)
        self.number_qoi = keywordArgs.get("numberQoI", 0)
        self.number_combined_qoi = keywordArgs.get("numberCombinedQoi", 0)
        self.number_multi_qoi = keywordArgs.get("numberMultiQoI", 0)
        self.number_multi_combined_qoi = keywordArgs.get(
            "numberMultiCombinedQoI", 0)
        self.outputBatchSize = keywordArgs.get('outputBatchSize', 1)
        self.print_to_file = keywordArgs.get("printToFile", False)
        self.project_parameters_path = keywordArgs.get('projectParametersPath')
        self.refinement_parameters_path = keywordArgs.get(
            'refinementParametersPath')
        self.refinement_strategy = keywordArgs.get('refinementStrategy')
        self.size_multi_x_qoi = keywordArgs.get(
            'sizeMultiXQoI',
            -1)  # remove after returnZeroQoiAndTime_Task is removed

        # Set outputDimension
        self.outputDimension = keywordArgs.get('outputDimension', None)
        # If not given, compute from self.outputBatchSize for backward compatibility
        if self.outputDimension is None:
            outputNb = self._numberOfOutputs()
            # Total number of output splits, including (possibly) a last one of smaller size
            batchNb = int(math.ceil(outputNb / self.outputBatchSize))
            # Assemble the list of sizes of each split
            # They are all equal to outputBatchSize, except perhaps the last one
            # E.g. outputBatchSize=2 and outputNb=5 gives [2,2,1]
            self.outputDimension = [
                min(self.outputBatchSize, outputNb - i * self.outputBatchSize)
                for i in range(batchNb)
            ]

        # workaround for Monte Carlo
        if (self.solverWrapperIndex == []):
            self.solverWrapperIndex.append(0)

        if (self.solverWrapperIndex[0] >= 0):  # for index < 0 not needed
            if (self.asynchronous is not True):  # synchronous framework
                self.serialize()
            else:  # asynchronous framework
                pass

    def serialize(self):
        """
        Method serializing Kratos Model and Kratos Parameters.

        Inputs:
        - self: an instance of the class.
        """

        if (self.refinement_strategy == "stochastic_adaptive_refinement"):
            # serialization
            self.is_project_parameters_pickled = False
            self.is_model_pickled = False
            self.is_custom_settings_metric_refinement_pickled = False
            self.is_custom_settings_remesh_refinement_pickled = False
            self.SetRefinementParameters()
            self.SerializeRefinementParameters()
            self.SerializeModelParameters()
            # estimate mesh size of current index
            self.ComputeMeshParameters()
        elif (self.refinement_strategy == "deterministic_adaptive_refinement"):
            # serialization
            self.is_project_parameters_pickled = False
            self.is_model_pickled = False
            self.is_custom_settings_metric_refinement_pickled = False
            self.is_custom_settings_remesh_refinement_pickled = False
            self.SetRefinementParameters()
            self.SerializeRefinementParameters()
            self.SerializeModelParameters()
            # estimate mesh size of current index
            self.ComputeMeshParameters()
        elif (self.refinement_strategy == "reading_from_file"):
            # serialization
            self.is_project_parameters_pickled = False
            self.is_model_pickled = False
            self.is_custom_settings_metric_refinement_pickled = False
            self.is_custom_settings_remesh_refinement_pickled = False
            self.SetRefinementParameters()
            self.SerializeRefinementParameters()
            self.SerializeModelParameters()
            # estimate mesh size of current index
            self.ComputeMeshParameters()
        else:
            raise Exception(
                "Select KratosMultiphysics refinement stategy.\nOptions:\
                \n   i) stochastic_adaptive_refinement\
                \n  ii) deterministic_adaptive_refinement\
                \n iii) reading_from_file")

    def solve(self, random_variable):
        """
        Method running the problem.

        Inputs:
        - self: an instance of the class
        - random_variable: random event in the form of list.

        Outputs:
        - qoi_list: list of structure respecting self.outputDimension. It contains the quantities of interest.
        - time_for_qoi: float. Measure of time to generate the sample.
        """

        if all([component >= 0 for component in self.solverWrapperIndex]):
            aux_qoi_array = []
            for contribution_counter in range(
                    0, self.number_contributions_per_instance):
                self.current_local_contribution = contribution_counter
                if (self.refinement_strategy ==
                        "stochastic_adaptive_refinement"):
                    qoi, time_for_qoi = self.executeInstanceStochasticAdaptiveRefinement(
                        random_variable)
                elif (self.refinement_strategy ==
                      "deterministic_adaptive_refinement"):
                    qoi, time_for_qoi = self.executeInstanceDeterministicAdaptiveRefinement(
                        random_variable)
                elif (self.refinement_strategy == "reading_from_file"):
                    qoi, time_for_qoi = self.executeInstanceReadingFromFile(
                        random_variable)
                # append components to aux array
                aux_qoi_array.append(qoi)
            # delete COMPSs future objects no longer needed
            delete_object(random_variable)

            # postprocess components
            if self.number_contributions_per_instance > 1:
                unm = mdu.UnfolderManager(self._numberOfOutputs(),
                                          self.outputBatchSize)
                if (self._numberOfOutputs() == self.outputBatchSize):
                    qoi_list = [
                        unm.PostprocessContributionsPerInstance(
                            aux_qoi_array, self.number_qoi,
                            self.number_combined_qoi)
                    ]
                elif (self._numberOfOutputs() > self.outputBatchSize):
                    qoi_list = unm.PostprocessContributionsPerInstance(
                        aux_qoi_array, self.number_qoi,
                        self.number_combined_qoi)
                else:
                    raise Exception(
                        "_numberOfOutputs() returns a value smaller than self.outputBatchSize. Set outputBatchSize smaller or equal to the number of scalar outputs."
                    )
                delete_object(unm)
            else:
                # unfold qoi into its components of fixed size
                unm = mdu.UnfolderManager(self._numberOfOutputs(),
                                          self.outputBatchSize)
                if (self._numberOfOutputs() == self.outputBatchSize):
                    qoi_list = [unm.UnfoldNValues_Task(aux_qoi_array[0])]
                elif (self._numberOfOutputs() > self.outputBatchSize):
                    qoi_list = unm.UnfoldNValues_Task(aux_qoi_array[0])
                else:
                    raise Exception(
                        "_numberOfOutputs() returns a value smaller than self.outputBatchSize. Set outputBatchSize smaller or equal to the number of scalar outputs."
                    )
                # delete COMPSs future objects no longer needed
                delete_object(unm)

            # delete COMPSs future objects no longer needed
            for contribution_counter in range(
                    0, self.number_contributions_per_instance):
                delete_object(aux_qoi_array[contribution_counter])
            delete_object(qoi)
            del (aux_qoi_array)

        else:
            qoi, time_for_qoi = mds.returnZeroQoiAndTime_Task(
                self.number_qoi + self.number_combined_qoi,
                self.number_multi_qoi + self.number_multi_combined_qoi,
                self.size_multi_x_qoi)
            # unfold qoi into its components of fixed size
            unm = mdu.UnfolderManager(self._numberOfOutputs(),
                                      self.outputBatchSize)
            if (self._numberOfOutputs() == self.outputBatchSize):
                qoi_list = [unm.UnfoldNValues_Task(qoi)]
            elif (self._numberOfOutputs() > self.outputBatchSize):
                qoi_list = unm.UnfoldNValues_Task(qoi)
            else:
                raise Exception(
                    "_numberOfOutputs() returns a value smaller than self.outputBatchSize. Set outputBatchSize smaller or equal to the number of scalar outputs."
                )
            # delete COMPSs future objects no longer needed
            delete_object(unm)

        return qoi_list, time_for_qoi

    ####################################################################################################
    ######################################### EXECUTION TOOLS ##########################################
    ####################################################################################################

    def executeInstanceStochasticAdaptiveRefinement(self, random_variable):
        """
        Method executing an instance of the UQ algorithm, i.e. a single MC realization and eventually the refinement (that occurs before the simulation run). To be called if the selected refinement strategy is stochastic_adaptive_refinement.

        Inputs:
        - self: an instance of the class.

        Outputs:
        - qoi: list. It contains the quantities of interest.
        - time_for_qoi: float. Measure of time to generate the sample.
        """

        current_index = self.solverWrapperIndex[0]
        # local variables
        pickled_coarse_model = self.pickled_model[0]
        pickled_reference_model_mapping = pickled_coarse_model
        pickled_coarse_project_parameters = self.pickled_project_parameters[0]
        pickled_custom_metric_refinement_parameters = self.pickled_custom_metric_refinement_parameters
        pickled_custom_remesh_refinement_parameters = self.pickled_custom_remesh_refinement_parameters
        current_analysis = self.analysis
        different_tasks = self.different_tasks
        mapping_flag = self.mapping_output_quantities
        adaptive_refinement_jump_to_finest_level = self.adaptive_refinement_jump_to_finest_level
        print_to_file = self.print_to_file
        current_local_contribution = self.current_local_contribution
        time_for_qoi = 0.0
        if (different_tasks is False):  # tasks all at once
            qoi,time_for_qoi = \
                mds.executeInstanceStochasticAdaptiveRefinementAllAtOnce_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_analysis,time_for_qoi,mapping_flag,adaptive_refinement_jump_to_finest_level,print_to_file,current_local_contribution)
        elif (different_tasks is True):  # multiple tasks
            if (current_index == 0):  # index = 0
                current_local_index = 0
                qoi,pickled_current_model,time_for_qoi = \
                    mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_local_index,current_analysis,time_for_qoi,mapping_flag,print_to_file,current_local_contribution)
                delete_object(pickled_current_model)
            else:  # index > 0
                for current_local_index in range(current_index + 1):
                    if ((adaptive_refinement_jump_to_finest_level is False) or
                        (adaptive_refinement_jump_to_finest_level is True and
                         (current_local_index == 0
                          or current_local_index == current_index))):
                        if (mapping_flag is False):
                            qoi,pickled_current_model,time_for_qoi = \
                                mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_local_index,current_analysis,time_for_qoi,mapping_flag,print_to_file,current_local_contribution)
                        elif (mapping_flag is True):
                            qoi,pickled_current_model,time_for_qoi = \
                                mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_local_index,current_analysis,time_for_qoi,mapping_flag,print_to_file,current_local_contribution,pickled_mapping_reference_model=pickled_reference_model_mapping)
                            delete_object(pickled_coarse_model)
                            del (pickled_coarse_model)
                        pickled_coarse_model = pickled_current_model
                        del (pickled_current_model)
                    else:  # not running since we jump from coarsest to finest level
                        pass
                delete_object(pickled_coarse_model)
        else:
            raise Exception(
                "Boolean variable different task is not a boolean, instead is equal to",
                different_tasks)
        return qoi, time_for_qoi

    def executeInstanceDeterministicAdaptiveRefinement(self, random_variable):
        """
        Method executing an instance of the UQ algorithm, i.e. a single MC realization and eventually the refinement (that occurs before the simulation run). To be called if the selected refinement strategy is deterministic_adaptive_refinement.

        Inputs:
        - self: an instance of the class.

        Outputs:
        - qoi: list. It contains the quantities of interest.
        - time_for_qoi: float. Measure of time to generate the sample.
        """

        # local variables
        current_index = self.solverWrapperIndex[0]
        pickled_model = self.pickled_model[current_index]
        pickled_project_parameters = self.pickled_project_parameters[0]
        current_analysis = self.analysis
        time_for_qoi = 0.0
        qoi, time_for_qoi = mds.executeInstanceDeterministicAdaptiveRefinement_Wrapper(
            current_index, pickled_model, pickled_project_parameters,
            current_analysis, random_variable, time_for_qoi)
        return qoi, time_for_qoi

    def executeInstanceReadingFromFile(self, random_variable):
        """
        Method executing an instance of the UQ algorithm, i.e. a single MC realization and eventually the refinement (that occurs before the simulation run). To be called if the selected refinement strategy is reading_from_file.

        Inputs:
        - self: an instance of the class.

        Outputs:
        - qoi: list. It contains the quantities of interest.
        - time_for_qoi: float. Measure of time to generate the sample.
        """

        # local variables
        current_index = self.solverWrapperIndex[0]
        pickled_model = self.pickled_model[current_index]
        pickled_mapping_reference_model = self.pickled_model[0]
        pickled_project_parameters = self.pickled_project_parameters[
            current_index]
        mapping_flag = self.mapping_output_quantities
        print_to_file = self.print_to_file
        current_local_contribution = self.current_local_contribution
        current_analysis = self.analysis
        time_for_qoi = 0.0
        qoi, time_for_qoi = mds.executeInstanceReadingFromFile_Wrapper(
            current_index, pickled_model, pickled_project_parameters,
            current_analysis, random_variable, time_for_qoi, mapping_flag,
            pickled_mapping_reference_model, print_to_file,
            current_local_contribution)
        return qoi, time_for_qoi

    ####################################################################################################
    ####################################### SERIALIZATION TOOLS ########################################
    ####################################################################################################

    def SerializeModelParameters(self):
        """
        Method managing the serialization and pickling of the Kratos Model and the Kratos Parameters of the problem. It builds self.pickled_model and self.pickled_project_parameters.
        The serialization-pickling process is the following:
        i)   from Model/Parameters Kratos object to MpiSerializer Kratos object,
        ii)  from MpiSerializer Kratos object to pickle string,
        iii) from pickle string to MpiSerializer Kratos object,
        iv)  from MpiSerializer Kratos object to Model/Parameters Kratos object.
        Depending on the refinement strategy, three different methods may be called and are defined next.

        Inputs:
        - self: an instance of the class.
        """

        self.serialized_model = []
        self.serialized_project_parameters = []
        self.pickled_model = []
        self.pickled_project_parameters = []

        if (self.refinement_strategy == "stochastic_adaptive_refinement"):
            self.SerializeModelParametersStochasticAdaptiveRefinement()
        elif (self.refinement_strategy == "deterministic_adaptive_refinement"):
            self.SerializeModelParametersDeterministicAdaptiveRefinement()
        elif (self.refinement_strategy == "reading_from_file"):
            self.SerializeModelParametersReadingFromFile()
        else:
            raise Exception(
                "Specify refinement_strategy: stochastic_adaptive_refinement or deterministic_adaptive_refinement or reading_from_file"
            )
        self.is_project_parameters_pickled = True
        self.is_model_pickled = True
        print("\n", "#" * 50,
              " SERIALIZATION MODEL AND PROJECT PARAMETERS COMPLETED ",
              "#" * 50, "\n")

    def SerializeModelParametersStochasticAdaptiveRefinement(self):
        """
        Method serializing and pickling the Kratos Model and the Kratos Parameters of the problem. It builds self.pickled_model and self.pickled_project_parameters. To be called if the selected refinement strategy is stochastic_adaptive_refinement.

        Inputs:
        - self: an instance of the class.
        """

        with open(self.project_parameters_path, 'r') as parameter_file:
            parameters = KratosMultiphysics.Parameters(parameter_file.read())
        # create wrapper instance to modify current project parameters
        self.wrapper = ParametersWrapper(parameters)
        # serialize parmeters (to avoid adding new data dependent on the application)
        parameters = self.wrapper.SetModelImportSettingsInputType(
            "use_input_model_part")
        serialized_project_parameters = KratosMultiphysics.MpiSerializer()
        serialized_project_parameters.Save("ParametersSerialization",
                                           parameters)
        self.serialized_project_parameters.append(
            serialized_project_parameters)
        # reset to read the model part
        parameters = self.wrapper.SetModelImportSettingsInputType("mdpa")
        # prepare the model to serialize
        model = KratosMultiphysics.Model()
        fake_sample = self.fake_sample_to_serialize
        simulation = self.analysis(model, parameters, fake_sample)
        simulation.Initialize()
        # reset general flags
        main_model_part_name = self.wrapper.GetModelPartName()
        simulation.model.GetModelPart(
            main_model_part_name).ProcessInfo.SetValue(
                KratosMultiphysics.IS_RESTARTED, True)
        # serialize model
        serialized_model = KratosMultiphysics.MpiSerializer()
        serialized_model.Save("ModelSerialization", simulation.model)
        self.serialized_model.append(serialized_model)
        # pickle dataserialized_data
        pickled_model = pickle.dumps(
            serialized_model, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        pickled_project_parameters = pickle.dumps(
            serialized_project_parameters, 2)
        self.pickled_model.append(pickled_model)
        self.pickled_project_parameters.append(pickled_project_parameters)

    def SerializeModelParametersDeterministicAdaptiveRefinement(self):
        """
        Method serializing and pickling the Kratos Model and the Kratos Parameters of the problem. It builds self.pickled_model and self.pickled_project_parameters. To be called if the selected refinement strategy is deterministic_adaptive_refinement.

        Inputs:
        - self: an instance of the class.
        """

        self.SerializeModelParametersStochasticAdaptiveRefinement(
        )  # to prepare parameters and model part of coarsest level
        number_levels_to_serialize = self.solverWrapperIndex[0]
        # same routine of ExecuteInstanceConcurrentAdaptiveRefinemnt() to build models and parameters, but here we save models and parameters
        pickled_coarse_model = self.pickled_model[0]
        pickled_coarse_project_parameters = self.pickled_project_parameters[0]
        pickled_custom_metric_refinement_parameters = self.pickled_custom_metric_refinement_parameters
        pickled_custom_remesh_refinement_parameters = self.pickled_custom_remesh_refinement_parameters
        current_analysis = self.analysis
        # generate the sample and prepare auxiliary variables we need
        fake_sample = self.fake_sample_to_serialize
        fake_computational_time = 0.0
        if (number_levels_to_serialize > 0):
            for current_level in range(number_levels_to_serialize + 1):
                fake_qoi,pickled_current_model,fake_computational_time = \
                    mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(number_levels_to_serialize,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,fake_sample,current_level,current_analysis,fake_computational_time,mapping_flag=False,print_to_file=False,current_contribution=0)
                del (pickled_coarse_model)
                pickled_coarse_model = pickled_current_model
                # save if current level > 0 (level = 0 has already been saved)
                if (current_level > 0):
                    # save pickled and serialized model and parameters
                    self.pickled_model.append(pickled_current_model)
                    self.serialized_model.append(
                        pickle.loads(pickled_current_model))
                del (pickled_current_model)

    def SerializeModelParametersReadingFromFile(self):
        """
        Method serializing and pickling the Kratos Model and the Kratos Parameters of the problem. It builds self.pickled_model and self.pickled_project_parameters. To be called if the selected refinement strategy is reading_from_file.

        Inputs:
        - self: an instance of the class.
        """

        for parameters_path in self.project_parameters_path:
            with open(parameters_path, 'r') as parameter_file:
                parameters = KratosMultiphysics.Parameters(
                    parameter_file.read())
            # create wrapper instance to modify current project parameters
            self.wrapper = ParametersWrapper(parameters)
            # serialize parmeters (to avoid adding new data dependent on the application)
            parameters = self.wrapper.SetModelImportSettingsInputType(
                "use_input_model_part")
            serialized_project_parameters = KratosMultiphysics.MpiSerializer()
            serialized_project_parameters.Save("ParametersSerialization",
                                               parameters)
            self.serialized_project_parameters.append(
                serialized_project_parameters)
            # reset to read the model part
            parameters = self.wrapper.SetModelImportSettingsInputType("mdpa")
            # prepare the model to serialize
            model = KratosMultiphysics.Model()
            fake_sample = self.fake_sample_to_serialize
            simulation = self.analysis(model, parameters, fake_sample)
            simulation.Initialize()
            # reset general flags
            main_model_part_name = self.wrapper.GetModelPartName()
            simulation.model.GetModelPart(
                main_model_part_name).ProcessInfo.SetValue(
                    KratosMultiphysics.IS_RESTARTED, True)
            # serialize model
            serialized_model = KratosMultiphysics.MpiSerializer()
            serialized_model.Save("ModelSerialization", simulation.model)
            self.serialized_model.append(serialized_model)
            # pickle dataserialized_data
            pickled_model = pickle.dumps(
                serialized_model, 2
            )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
            pickled_project_parameters = pickle.dumps(
                serialized_project_parameters, 2)
            self.pickled_model.append(pickled_model)
            self.pickled_project_parameters.append(pickled_project_parameters)

    def SerializeRefinementParameters(self):
        """
        Method serializing and pickling the custom setting metric and remeshing for the adaptive refinement. It requires self.custom_metric_refinement_parameters and self.custom_remesh_refinement_parameters. It builds self.pickled_custom_metric_refinement_parameters and self.pickled_custom_remesh_refinement_parameters.

        Inputs:
        - self: an instance of the class.
        """

        metric_refinement_parameters = self.custom_metric_refinement_parameters
        remeshing_refinement_parameters = self.custom_remesh_refinement_parameters
        # save parameters as MpiSerializer Kratos objects
        serialized_metric_refinement_parameters = KratosMultiphysics.MpiSerializer(
        )
        serialized_metric_refinement_parameters.Save(
            "MetricRefinementParametersSerialization",
            metric_refinement_parameters)
        serialized_remesh_refinement_parameters = KratosMultiphysics.MpiSerializer(
        )
        serialized_remesh_refinement_parameters.Save(
            "RemeshRefinementParametersSerialization",
            remeshing_refinement_parameters)
        # pickle parameters
        pickled_metric_refinement_parameters = pickle.dumps(
            serialized_metric_refinement_parameters, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        pickled_remesh_refinement_parameters = pickle.dumps(
            serialized_remesh_refinement_parameters, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        self.pickled_custom_metric_refinement_parameters = pickled_metric_refinement_parameters
        self.pickled_custom_remesh_refinement_parameters = pickled_remesh_refinement_parameters
        self.is_custom_settings_metric_refinement_pickled = True
        self.is_custom_settings_remesh_refinement_pickled = True
        print("\n", "#" * 50,
              " SERIALIZATION REFINEMENT PARAMETERS COMPLETED ", "#" * 50,
              "\n")

    ####################################################################################################
    ######################################### AUXILIARY TOOLS ##########################################
    ####################################################################################################

    def SetRefinementParameters(self):
        """
        Method reading the refinement parameters passed from json file.

        Inputs:
        - self: an instance of the class.
        """

        with open(self.refinement_parameters_path, 'r') as parameter_file:
            parameters = KratosMultiphysics.Parameters(parameter_file.read())
        self.custom_metric_refinement_parameters = parameters["hessian_metric"]
        self.custom_remesh_refinement_parameters = parameters["refinement_mmg"]

    def ComputeMeshParameters(self):
        """
        Method computing the mesh discretization parameter self.mesh_parameters and the mesh sizes self.mesh_sizes. The mesh parameter is the reciprocal of the minimum mesh size of the grid.

        Inputs:
        - self: an instance of the class.
        """

        # unpickle and unserialize model and build Kratos Model object
        serialized_model = pickle.loads(self.pickled_model[0])
        current_model = KratosMultiphysics.Model()
        serialized_model.Load("ModelSerialization", current_model)
        # unpickle and unserialize parameters and build Kratos Parameters object
        serialized_project_parameters = pickle.loads(
            self.pickled_project_parameters[0])
        current_project_parameters = KratosMultiphysics.Parameters()
        serialized_project_parameters.Load("ParametersSerialization",
                                           current_project_parameters)
        # unpickle and unserialize metric refinement parameters and build Kratos Parameters objects
        serialized_custom_metric_refinement_parameters = pickle.loads(
            self.pickled_custom_metric_refinement_parameters)
        current_custom_metric_refinement_parameters = KratosMultiphysics.Parameters(
        )
        serialized_custom_metric_refinement_parameters.Load(
            "MetricRefinementParametersSerialization",
            current_custom_metric_refinement_parameters)
        self.mesh_sizes = []
        self.mesh_parameters = []
        level = self.solverWrapperIndex[0]
        adaptive_refinement_manager = AdaptiveRefinement(
            level, current_model, current_project_parameters,
            current_custom_metric_refinement_parameters, None)
        adaptive_refinement_manager.EstimateMeshSizeCurrentLevel()
        h_current_level = adaptive_refinement_manager.mesh_size
        mesh_parameter_current_level = h_current_level**(-1)
        self.mesh_sizes.append(h_current_level)
        self.mesh_parameters.append(mesh_parameter_current_level)

    def _numberOfOutputs(self):
        """
        Internal method returning the total number of outputs, regardless of how
        how many members vector quantities of interest have.

        Inputs:
        - self: an instance of the class.
        """
        return self.number_qoi + self.number_combined_qoi + self.number_multi_qoi + self.number_multi_combined_qoi
Example #3
0
class AdaptiveRefinement(object):
    """
    Class managing the adaptive refinement process, called when executing Multilevel Monte Carlo algorithms.
    This class handles the call to the MeshingApplication, and to other applications, if needed along the process.

    Input:
    - model_coarse: Kratos model class before refinement
    - parameters_coarse: Kratos parameters class before refinement
    - minimal_size_value: minimal size after remeshing
    - maximal_size_value: maximal size after remeshing
    - metric_param: Kratos parameters class containing metric custom settings
    - remesh_param: Kratos parameters class containing remeshing custom settings
    - metric_name: string defining the metring which the class will use to build the metric
    """
    def __init__(self,
                 current_level,
                 model_coarse,
                 parameters_coarse,
                 metric_param,
                 remesh_param,
                 metric_name="hessian"):
        self.model_coarse = model_coarse
        self.parameters_coarse = parameters_coarse
        self.metric_param = metric_param
        self.remesh_param = remesh_param
        self.problem_type = self.parameters_coarse["solver_settings"][
            "solver_type"].GetString()
        self.metric = metric_name
        self.current_level = current_level
        self.wrapper = ParametersWrapper(self.parameters_coarse)

    def ComputeAdaptiveRefinement(self):
        """
        Method computing the refinement of the model based on the solution on the coarse mesh,
        exploiting the hessian metric of the solution.

        Input:
        - self: an instance of the class

        Output:
        - current_model_refined : Kratos model class after refinement
        - current_parameters_refined : Kratos parameters class after refinement
        """
        parameters_coarse = self.parameters_coarse
        model_coarse = self.model_coarse
        metric_param = self.metric_param
        remesh_param = self.remesh_param
        problem_type = self.problem_type
        current_level = self.current_level

        # check MeshingApplication is imported,
        # otherwise raise an error
        if not CheckIfApplicationsAvailable("MeshingApplication"):
            raise Exception(
                "[MultilevelMonteCarloApplication]: MeshingApplication cannot be imported, but it is necessary to perform adaptive refinement."
            )

        if (self.metric is "hessian"):
            # initialize interpolation error
            original_interp_error = metric_param[
                "hessian_strategy_parameters"][
                    "interpolation_error"].GetDouble()
            # set interpolation error for current level
            if current_level > 0:
                coefficient_interp_error = metric_param[
                    "hessian_strategy_parameters"][
                        "coefficient_interpolation_error"].GetDouble()
                metric_param["hessian_strategy_parameters"].RemoveValue(
                    "coefficient_interpolation_error")
                interp_error = original_interp_error * (
                    coefficient_interp_error)**(-current_level)
                # interp_error = original_interp_error/(coefficient_interp_error*current_level)
                metric_param["hessian_strategy_parameters"][
                    "interpolation_error"].SetDouble(interp_error)
            # Setting metric tensor to 0
            domain_size = self.wrapper.GetDomainSize()
            model_part_name = parameters_coarse["solver_settings"][
                "model_part_name"].GetString()
            if domain_size == 2:
                KratosMultiphysics.VariableUtils(
                ).SetNonHistoricalVariableToZero(
                    KratosMultiphysics.MeshingApplication.METRIC_TENSOR_2D,
                    model_coarse.GetModelPart(model_part_name).Nodes)
            elif domain_size == 3:
                KratosMultiphysics.VariableUtils(
                ).SetNonHistoricalVariableToZero(
                    KratosMultiphysics.MeshingApplication.METRIC_TENSOR_3D,
                    model_coarse.GetModelPart(model_part_name).Nodes)
            else:
                err_msg = "Domain size is {}. Supported values are 2 and 3.\n".format(
                    domain_size)
            # calculate NODAL_H
            find_nodal_h = KratosMultiphysics.FindNodalHNonHistoricalProcess(
                model_coarse.GetModelPart(model_part_name))
            find_nodal_h.Execute()

            # build the metric
            if metric_param["hessian_strategy_parameters"].Has(
                    "metric_variable"):
                metric_variables = self.__generate_variable_list_from_input(
                    metric_param["hessian_strategy_parameters"]
                    ["metric_variable"])
                # remove metric value from settings, since we pass it directly in the constructor
                metric_param["hessian_strategy_parameters"].RemoveValue(
                    "metric_variable")
            else:
                raise Exception(
                    "A list of variable is expected under the key [\"hessian_strategy_parameters\"][\"metric_variable\"] of the \"metric\" dictionary."
                )

            for mv in metric_variables:
                local_gradient = KratosMultiphysics.MeshingApplication.ComputeHessianSolMetricProcess(
                    model_coarse.GetModelPart(model_part_name), mv,
                    metric_param)
                local_gradient.Execute()

            # create the remeshing process and execute it
            if KratosMultiphysics.IsDistributedRun():  # MPI
                KratosMultiphysics.mpi.ParallelFillCommunicator(
                    model_coarse.GetModelPart(model_part_name)).Execute()
                if domain_size == 3:
                    if (hasattr(KratosMultiphysics.MeshingApplication,
                                "ParMmgProcess3D")):
                        pmmg_process = KratosMultiphysics.MeshingApplication.ParMmgProcess3D(
                            model_coarse.GetModelPart(model_part_name),
                            remesh_param)
                    else:
                        raise Exception(
                            "[MultilevelMonteCarloApplication]: ParMmgProcess3D atrribute not found within MeshingApplcation. It is required to perform remeshing."
                        )
                else:
                    err_msg = "Domain size is {}. Supported value is 3.\n".format(
                        domain_size)
                    raise Exception(err_msg)
                pmmg_process.Execute()
            else:  # serial
                if domain_size == 2:
                    if (hasattr(KratosMultiphysics.MeshingApplication,
                                "MmgProcess2D")):
                        mmg_process = KratosMultiphysics.MeshingApplication.MmgProcess2D(
                            model_coarse.GetModelPart(model_part_name),
                            remesh_param)
                    else:
                        raise Exception(
                            "[MultilevelMonteCarloApplication]: MmgProcess2D atrribute not found within MeshingApplcation. It is required to perform remeshing."
                        )
                elif domain_size == 3:
                    if (hasattr(KratosMultiphysics.MeshingApplication,
                                "MmgProcess3D")):
                        mmg_process = KratosMultiphysics.MeshingApplication.MmgProcess3D(
                            model_coarse.GetModelPart(model_part_name),
                            remesh_param)
                    else:
                        raise Exception(
                            "[MultilevelMonteCarloApplication]: MmgProcess3D atrribute not found within MeshingApplcation. It is required to perform remeshing."
                        )
                else:
                    err_msg = "Domain size is {}. Supported values are 2 and 3.\n".format(
                        domain_size)
                    raise Exception(err_msg)
                mmg_process.Execute()

            # reset variables if needed
            model_coarse.GetModelPart(model_part_name).ProcessInfo.SetValue(
                KratosMultiphysics.TIME, 0.0)
            model_coarse.GetModelPart(model_part_name).ProcessInfo.SetValue(
                KratosMultiphysics.STEP, 0)
            model_coarse.GetModelPart(model_part_name).ProcessInfo.SetValue(
                KratosMultiphysics.IS_RESTARTED, False)

            if (problem_type
                    in ["monolithic", "FractionalStep", "potential_flow"]):
                model_coarse.GetModelPart(model_part_name).RemoveSubModelPart(
                    "fluid_computational_model_part")

            # the refinement process empties the coarse model part object and fill it with the refined model part
            # the solution on the refined grid is obtained from the interpolation of the coarse solution
            # there are not other operations, therefore to build the new model we just need to take the updated coarse model
            current_model_refined = model_coarse
            current_parameters_refined = parameters_coarse
            return current_model_refined, current_parameters_refined

        else:
            err_msg = "Metric passed to the AdaptiveRefinement class is {}, but the only supported metric is \"hessian\".\n".format(
                self.metric)
            raise Exception(err_msg)

    def ComputeMeshSizeCoarsestLevel(self):
        """
        Method computing the mesh size of coarsest level, estimated as minimum nodal_h.

        Input:
        - self: an instance of the class
        """
        model_coarse = self.model_coarse
        model_part_name = self.wrapper.GetModelPartName()
        # set NODAL_AREA and NODAL_H as non historical variables
        KratosMultiphysics.VariableUtils().SetNonHistoricalVariable(
            KratosMultiphysics.NODAL_AREA, 0.0,
            model_coarse.GetModelPart(model_part_name).Nodes)
        KratosMultiphysics.VariableUtils().SetNonHistoricalVariable(
            KratosMultiphysics.NODAL_H, 0.0,
            model_coarse.GetModelPart(model_part_name).Nodes)
        # calculate NODAL_H
        find_nodal_h = KratosMultiphysics.FindNodalHProcess(
            model_coarse.GetModelPart(model_part_name))
        find_nodal_h = KratosMultiphysics.FindNodalHNonHistoricalProcess(
            model_coarse.GetModelPart(model_part_name))
        find_nodal_h.Execute()
        # compute average mesh size
        mesh_size = 10.0
        for node in model_coarse.GetModelPart(model_part_name).Nodes:
            if (node.GetValue(KratosMultiphysics.NODAL_H) < mesh_size):
                mesh_size = node.GetValue(KratosMultiphysics.NODAL_H)
        self.mesh_size_coarsest_level = mesh_size

    def EstimateMeshSizeCurrentLevel(self):
        """
        Method estimating the mesh size of current level.

        Input:
        - self: an instance of the class
        """
        self.ComputeMeshSizeCoarsestLevel()
        current_level = self.current_level
        if (self.metric is "hessian"):
            original_interp_error = self.metric_param[
                "hessian_strategy_parameters"][
                    "interpolation_error"].GetDouble()
            domain_size = self.wrapper.GetDomainSize()
            if (domain_size == 2):
                coefficient = 2 / 9  # 2d
            elif (domain_size == 3):
                coefficient = 9 / 32  # 3d
            # TODO: compute below interp error level more automatically
            coefficient_interp_error = self.metric_param[
                "hessian_strategy_parameters"][
                    "coefficient_interpolation_error"].GetDouble()
            # interp_error_level = original_interp_error*(coefficient_interp_error)**(-current_level)
            if (current_level > 0):
                interp_error_level = original_interp_error / (
                    coefficient_interp_error * current_level)
            else:  # current_level == 0
                interp_error_level = original_interp_error
            mesh_size_level = self.mesh_size_coarsest_level * sqrt(
                interp_error_level / original_interp_error
            )  # relation from [Alauzet] eqs. pag 34 and 35
            self.mesh_size = mesh_size_level

    def __generate_variable_list_from_input(self, param):
        """
        Method taken and adapted from mmg_progess.py of MeshingApplication.
        Parse a list of variables from input.
        """
        # At least verify that the input is a string
        if not param.IsArray():
            raise Exception("{0} Error: Variable list is unreadable".format(
                self.__class__.__name__))

        # Retrieve variable name from input (a string) and request the corresponding C++ object to the kernel
        variable_list = []
        param_names = param.GetStringArray()
        for variable_name in param_names:
            varriable_type = KratosMultiphysics.KratosGlobals.GetVariableType(
                variable_name)
            if varriable_type == "Double" or varriable_type == "Component":
                variable_list.append(
                    KratosMultiphysics.KratosGlobals.GetVariable(
                        variable_name))
            else:
                variable_list.append(
                    KratosMultiphysics.KratosGlobals.GetVariable(
                        variable_name + "_X"))
                variable_list.append(
                    KratosMultiphysics.KratosGlobals.GetVariable(
                        variable_name + "_Y"))
                if self.wrapper.GetDomainSize() == 3:
                    variable_list.append(
                        KratosMultiphysics.KratosGlobals.GetVariable(
                            variable_name + "_Z"))

        return variable_list
class AdaptiveRefinement(object):
    """
    input:  model_coarse       : Kratos model class before refinement
            parameters_coarse  : Kratos parameters class before refinement
            minimal_size_value : minimal size after remeshing
            maximal_size_value : maximal size after remeshing
            metric_param       : Kratos parameters class containing metric custom settings
            remesh_param       : Kratos parameters class containing remeshing custom settings
    """
    def __init__(self,
                 current_level,
                 model_coarse,
                 parameters_coarse,
                 metric_param,
                 remesh_param,
                 metric_name="hessian"):
        self.model_coarse = model_coarse
        self.parameters_coarse = parameters_coarse
        self.metric_param = metric_param
        self.remesh_param = remesh_param
        self.problem_type = self.parameters_coarse["solver_settings"][
            "solver_type"].GetString()
        self.metric = metric_name
        self.current_level = current_level
        self.wrapper = ParametersWrapper(self.parameters_coarse)

    """
    function computing the refinement of the model based on the solution on the coarse mesh,
    exploiting the hessian metric of the solution
    input:  self: an instance of the class
    output: current_model_refined      : Kratos model class after refinement
            current_parameters_refined : Kratos parameters class after refinement
    """

    def ComputeAdaptiveRefinement(self):
        parameters_coarse = self.parameters_coarse
        model_coarse = self.model_coarse
        metric_param = self.metric_param
        remesh_param = self.remesh_param
        problem_type = self.problem_type
        current_level = self.current_level

        if (self.metric is "hessian"):
            original_interp_error = metric_param[
                "hessian_strategy_parameters"][
                    "interpolation_error"].GetDouble()

            # problem dependent section
            if (problem_type == "potential_flow"):
                model_part_name = parameters_coarse["solver_settings"][
                    "model_part_name"].GetString()
                # set NODAL_AREA and NODAL_H as non historical variables
                KratosMultiphysics.VariableUtils().SetNonHistoricalVariable(
                    KratosMultiphysics.NODAL_AREA, 0.0,
                    model_coarse.GetModelPart(model_part_name).Nodes)
                KratosMultiphysics.VariableUtils().SetNonHistoricalVariable(
                    KratosMultiphysics.NODAL_H, 0.0,
                    model_coarse.GetModelPart(model_part_name).Nodes)
                # Setting Metric Tensor to 0
                KratosMultiphysics.VariableUtils(
                ).SetNonHistoricalVariableToZero(
                    KratosMultiphysics.MeshingApplication.METRIC_TENSOR_2D,
                    model_coarse.GetModelPart(model_part_name).Nodes)
                # calculate NODAL_H
                find_nodal_h = KratosMultiphysics.FindNodalHProcess(
                    model_coarse.GetModelPart(model_part_name))
                find_nodal_h = KratosMultiphysics.FindNodalHNonHistoricalProcess(
                    model_coarse.GetModelPart(model_part_name))
                find_nodal_h.Execute()
                custom_gradient = KratosMultiphysics.CompressiblePotentialFlowApplication.ComputeCustomNodalGradientProcess(
                    model_coarse.GetModelPart(model_part_name),
                    KratosMultiphysics.VELOCITY, KratosMultiphysics.NODAL_AREA)
                custom_gradient.Execute()

                if metric_param.Has("local_gradient_variable"):
                    metric_param.RemoveValue("local_gradient_variable")
                if current_level > 0:
                    coefficient_interp_error = metric_param[
                        "hessian_strategy_parameters"][
                            "coefficient_interpolation_error"].GetDouble()
                    metric_param["hessian_strategy_parameters"].RemoveValue(
                        "coefficient_interpolation_error")
                    # interp_error = original_interp_error*(coefficient_interp_error)**(-current_level)
                    interp_error = original_interp_error / (
                        coefficient_interp_error * current_level)
                    metric_param["hessian_strategy_parameters"][
                        "interpolation_error"].SetDouble(interp_error)

                local_gradient = KratosMeshing.ComputeHessianSolMetricProcess(
                    model_coarse.GetModelPart(model_part_name),
                    KratosMultiphysics.VELOCITY_X, metric_param)
                local_gradient.Execute()
                local_gradient = KratosMeshing.ComputeHessianSolMetricProcess(
                    model_coarse.GetModelPart(model_part_name),
                    KratosMultiphysics.VELOCITY_Y, metric_param)
                local_gradient.Execute()

                # #### OLD APPROACH
                # for node in model_coarse.GetModelPart(model_part_name).Nodes:
                #     vector=node.GetValue(KratosMultiphysics.VELOCITY)
                #     norm=np.linalg.norm(vector)
                #     node.SetSolutionStepValue(KratosMultiphysics.TEMPERATURE,norm)

                # prepare parameters to calculate the gradient of the designed variable
                # local_gradient_variable_string = metric_param["local_gradient_variable"].GetString()
                # local_gradient_variable = KratosMultiphysics.KratosGlobals.GetVariable(metric_param["local_gradient_variable"].GetString())
                # set interpolation error value (level dependent)
                # calculate the gradient of the variable
                # local_gradient = KratosMeshing.ComputeHessianSolMetricProcess(model_coarse.GetModelPart(model_part_name),local_gradient_variable,metric_param)
                # local_gradient.Execute()

                # # add again the removed variable parameter
                # metric_param.AddEmptyValue("local_gradient_variable")
                # metric_param["local_gradient_variable"].SetString(local_gradient_variable_string)

                #### OLD APPROACH

            elif (problem_type == "monolithic"):
                if metric_param.Has("local_gradient_variable"):
                    metric_param.RemoveValue("local_gradient_variable")
                if current_level > 0:
                    coefficient_interp_error = metric_param[
                        "hessian_strategy_parameters"][
                            "coefficient_interpolation_error"].GetDouble()
                    metric_param["hessian_strategy_parameters"].RemoveValue(
                        "coefficient_interpolation_error")
                    # interp_error = original_interp_error*(coefficient_interp_error)**(-current_level)
                    interp_error = original_interp_error / (
                        coefficient_interp_error * current_level)
                    metric_param["hessian_strategy_parameters"][
                        "interpolation_error"].SetDouble(interp_error)
                model_part_name = parameters_coarse["solver_settings"][
                    "model_part_name"].GetString()

                # Setting Metric Tensor to 0
                KratosMultiphysics.VariableUtils(
                ).SetNonHistoricalVariableToZero(
                    KratosMultiphysics.MeshingApplication.METRIC_TENSOR_2D,
                    model_coarse.GetModelPart(model_part_name).Nodes)

                # calculate NODAL_H
                find_nodal_h = KratosMultiphysics.FindNodalHNonHistoricalProcess(
                    model_coarse.GetModelPart(model_part_name))
                find_nodal_h.Execute()
                local_gradient = KratosMeshing.ComputeHessianSolMetricProcess(
                    model_coarse.GetModelPart(model_part_name),
                    KratosFluid.AVERAGE_VELOCITY_X, metric_param)
                local_gradient.Execute()
                local_gradient = KratosMeshing.ComputeHessianSolMetricProcess(
                    model_coarse.GetModelPart(model_part_name),
                    KratosFluid.AVERAGE_VELOCITY_Y, metric_param)
                local_gradient.Execute()

            elif (problem_type == "stationary"):
                if current_level > 0:
                    coefficient_interp_error = metric_param[
                        "hessian_strategy_parameters"][
                            "coefficient_interpolation_error"].GetDouble()
                    metric_param["hessian_strategy_parameters"].RemoveValue(
                        "coefficient_interpolation_error")
                    interp_error = original_interp_error * (
                        coefficient_interp_error)**(-current_level)
                    metric_param["hessian_strategy_parameters"][
                        "interpolation_error"].SetDouble(interp_error)
                model_part_name = parameters_coarse["solver_settings"][
                    "model_part_name"].GetString()
                # Setting Metric Tensor to 0
                KratosMultiphysics.VariableUtils(
                ).SetNonHistoricalVariableToZero(
                    KratosMultiphysics.MeshingApplication.METRIC_TENSOR_2D,
                    model_coarse.GetModelPart(model_part_name).Nodes)
                # calculate NODAL_H
                find_nodal_h = KratosMultiphysics.FindNodalHNonHistoricalProcess(
                    model_coarse.GetModelPart(model_part_name))
                find_nodal_h.Execute()
                local_gradient = KratosMeshing.ComputeHessianSolMetricProcess(
                    model_coarse.GetModelPart(model_part_name),
                    KratosMultiphysics.TEMPERATURE, metric_param)
                local_gradient.Execute()

            # create the remeshing process
            MmgProcess = KratosMeshing.MmgProcess2D(
                model_coarse.GetModelPart(model_part_name), remesh_param)
            MmgProcess.Execute()

            # reset variables if needed
            model_coarse.GetModelPart(model_part_name).ProcessInfo.SetValue(
                KratosMultiphysics.TIME, 0.0)
            model_coarse.GetModelPart(model_part_name).ProcessInfo.SetValue(
                KratosMultiphysics.STEP, 0)
            """
            the refinement process empties the coarse model part object and fill it with the refined model part
            the solution on the refined grid is obtained from the interpolation of the coarse solution
            there are not other operations, therefore to build the new model we just need to take the updated coarse model
            """
            current_model_refined = model_coarse
            current_parameters_refined = parameters_coarse
            return current_model_refined, current_parameters_refined

    """
    method computing the mesh size of coarsest level, estimated as minimum nodal_h
    input:  self : an instance of the class
    """

    def ComputeMeshSizeCoarsestLevel(self):
        model_coarse = self.model_coarse
        parameters_coarse = self.parameters_coarse
        model_part_name = self.wrapper.GetModelPartName()
        # set NODAL_AREA and NODAL_H as non historical variables
        KratosMultiphysics.VariableUtils().SetNonHistoricalVariable(
            KratosMultiphysics.NODAL_AREA, 0.0,
            model_coarse.GetModelPart(model_part_name).Nodes)
        KratosMultiphysics.VariableUtils().SetNonHistoricalVariable(
            KratosMultiphysics.NODAL_H, 0.0,
            model_coarse.GetModelPart(model_part_name).Nodes)
        # calculate NODAL_H
        find_nodal_h = KratosMultiphysics.FindNodalHProcess(
            model_coarse.GetModelPart(model_part_name))
        find_nodal_h = KratosMultiphysics.FindNodalHNonHistoricalProcess(
            model_coarse.GetModelPart(model_part_name))
        find_nodal_h.Execute()
        # compute average mesh size
        mesh_size = 10.0
        for node in model_coarse.GetModelPart(model_part_name).Nodes:
            if (node.GetValue(KratosMultiphysics.NODAL_H) < mesh_size):
                mesh_size = node.GetValue(KratosMultiphysics.NODAL_H)
        self.mesh_size_coarsest_level = mesh_size

    """
    method estimating the mesh size of current level
    input:  self : an instance of the class
    """

    def EstimateMeshSizeCurrentLevel(self):
        self.ComputeMeshSizeCoarsestLevel()
        current_level = self.current_level
        if (self.metric is "hessian"):
            original_interp_error = self.metric_param[
                "hessian_strategy_parameters"][
                    "interpolation_error"].GetDouble()
            domain_size = self.wrapper.GetDomainSize()
            if (domain_size == 2):
                coefficient = 2 / 9  # 2d
            elif (domain_size == 3):
                coefficient = 9 / 32  # 3d
            # TODO: compute below interp error level more automatically
            coefficient_interp_error = self.metric_param[
                "hessian_strategy_parameters"][
                    "coefficient_interpolation_error"].GetDouble()
            # interp_error_level = original_interp_error*(coefficient_interp_error)**(-current_level)
            if (current_level > 0):
                interp_error_level = original_interp_error / (
                    coefficient_interp_error * current_level)
            else:  # current_level == 0
                interp_error_level = original_interp_error
            mesh_size_level = self.mesh_size_coarsest_level * np.sqrt(
                interp_error_level / original_interp_error
            )  # relation from [Alauzet] eqs. pag 34 and 35
            self.mesh_size = mesh_size_level
Example #5
0
class KratosSolverWrapper(sw.SolverWrapper):

    # TODO: solverWrapperIndex will be removed from here and will have an indicator about the level we are at and not which algorithm we are using
    def __init__(self, **keywordArgs):
        super().__init__(**keywordArgs)
        self.analysis = SimulationScenario
        self.adaptive_refinement_jump_to_finest_level = keywordArgs.get(
            "adaptiveRefinementJumpToFinestLevel", False)
        self.asynchronous = keywordArgs.get("asynchronous", False)
        self.fake_sample_to_serialize = keywordArgs.get('fakeRandomVariable')
        self.mapping_output_quantities = keywordArgs.get(
            "mappingOutputQuantities", False)
        self.number_contributions_per_instance = keywordArgs.get(
            "numberContributionsPerInstance", 2)
        self.number_qoi = keywordArgs.get("numberQoI", 1)
        self.number_combined_qoi = keywordArgs.get("numberCombinedQoi", 0)
        self.print_to_file = keywordArgs.get("printToFile", False)
        self.project_parameters_path = keywordArgs.get('projectParametersPath')
        self.refinement_parameters_path = keywordArgs.get(
            'refinementParametersPath')
        self.refinement_strategy = keywordArgs.get('refinementStrategy')
        self.different_tasks = not keywordArgs.get('taskAllAtOnce', False)

        # TODO: remove this hard code to run MC
        if (self.solverWrapperIndex == []):
            self.solverWrapperIndex.append(0)

        if (self.solverWrapperIndex[0] >=
                0):  # for index < 0 nothing is needed
            if (self.asynchronous is not True):  # synchronous framework
                self.serialize()
            else:  # asynchronous framework
                pass

    def serialize(self):
        if (self.refinement_strategy == "stochastic_adaptive_refinement"):
            # serialization
            self.is_project_parameters_pickled = False
            self.is_model_pickled = False
            self.is_custom_settings_metric_refinement_pickled = False
            self.is_custom_settings_remesh_refinement_pickled = False
            self.SetRefinementParameters()
            self.SerializeRefinementParameters()
            self.SerializeModelParameters()
            # estimate mesh size of current index
            self.ComputeMeshParameters()

        elif (self.refinement_strategy == "deterministic_adaptive_refinement"):
            # serialization
            self.is_project_parameters_pickled = False
            self.is_model_pickled = False
            self.is_custom_settings_metric_refinement_pickled = False
            self.is_custom_settings_remesh_refinement_pickled = False
            self.SetRefinementParameters()
            self.SerializeRefinementParameters()
            self.SerializeModelParameters()
            # estimate mesh size of current index
            self.ComputeMeshParameters()

        elif (self.refinement_strategy == "reading_from_file"):
            # TODO - Change this implementation for later
            # If solverWrapperIndex == [], then projectParametersPath should indicate the absolute file
            # Else, projectParametersPath should be a prefix such that (number).json will be appeneded
            if (self.solverWrapperIndex[0] >= 0):
                # reading of json file
                self.project_parameters_path = self.project_parameters_path + '_' + str(
                    self.solverWrapperIndex[0]) + '.json'
                # serialization
                self.is_project_parameters_pickled = False
                self.is_model_pickled = False
                self.SerializeModelParameters()
            else:
                pass

        else:
            raise Exception(
                "Select KratosMultiphysics refinement stategy.\nOptions:\
                \n   i) stochastic_adaptive_refinement\
                \n  ii) deterministic_adaptive_refinement\
                \n iii) reading_from_file")

    def solve(self, random_variable):
        if all([component >= 0 for component in self.solverWrapperIndex]):
            aux_qoi_array = [
                []
                for _ in range(0, self.number_qoi + self.number_combined_qoi)
            ]  # to store each qoi
            for _ in range(0, self.number_contributions_per_instance):
                if (self.refinement_strategy ==
                        "stochastic_adaptive_refinement"):
                    qoi, time_for_qoi = self.executeInstanceStochasticAdaptiveRefinement(
                        random_variable)
                elif (self.refinement_strategy ==
                      "deterministic_adaptive_refinement"):
                    qoi, time_for_qoi = self.executeInstanceDeterministicAdaptiveRefinement(
                        random_variable)
                elif (self.refinement_strategy == "reading_from_file"):
                    qoi, time_for_qoi = self.executeInstanceReadingFromFile(
                        random_variable)
                # unfold qoi into its components
                if ((self.number_qoi + self.number_combined_qoi) == 1):
                    qoi_list = mdu.unfoldVales_Wrapper(self.number_qoi, qoi)
                else:
                    qoi_list = mdu.UnfolderManager(
                        self.number_qoi +
                        self.number_combined_qoi).UnfoldNValues_Task(qoi)
                # append components to aux array
                for qoi_counter in range(
                        0, self.number_qoi + self.number_combined_qoi):
                    aux_qoi_array[qoi_counter].append(qoi_list[qoi_counter])
            # postprocess components
            qoi_postprocessed = mdu.PostprocessContributionsPerInstance(
                aux_qoi_array, self.number_qoi, self.number_combined_qoi)
            # unfold qoi into its components
            if ((self.number_qoi + self.number_combined_qoi) == 1):
                qoi_list = mdu.unfoldVales_Wrapper(self.number_qoi,
                                                   qoi_postprocessed)
            else:
                qoi_list = mdu.UnfolderManager(
                    self.number_qoi +
                    self.number_combined_qoi).UnfoldNValues_Task(
                        qoi_postprocessed)
        else:
            qoi, time_for_qoi = mds.returnZeroQoIandTime_Task()
            qoi_list = [qoi] * (self.number_qoi + self.number_combined_qoi)
        return qoi_list, time_for_qoi

    ####################################################################################################
    ######################################### EXECUTION TOOLS ##########################################
    ####################################################################################################
    """
    function executing an instance of the UQ algorithm, i.e. a single MC simulation and eventually the refinement (that occurs before the simulation run)
    input:  self: an instance of the class
    output: qoi: quantity of interest value
    """

    def executeInstanceStochasticAdaptiveRefinement(self, random_variable):
        current_index = self.solverWrapperIndex[0]
        # local variables
        pickled_coarse_model = self.pickled_model[0]
        pickled_reference_model_mapping = pickled_coarse_model
        pickled_coarse_project_parameters = self.pickled_project_parameters[0]
        pickled_custom_metric_refinement_parameters = self.pickled_custom_metric_refinement_parameters
        pickled_custom_remesh_refinement_parameters = self.pickled_custom_remesh_refinement_parameters
        current_analysis = self.analysis
        different_tasks = self.different_tasks
        mapping_flag = self.mapping_output_quantities
        adaptive_refinement_jump_to_finest_level = self.adaptive_refinement_jump_to_finest_level
        print_to_file = self.print_to_file
        time_for_qoi = 0.0
        if (different_tasks is False):  # tasks all at once
            qoi,time_for_qoi = \
                mds.executeInstanceStochasticAdaptiveRefinementAllAtOnce_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_analysis,time_for_qoi,mapping_flag,adaptive_refinement_jump_to_finest_level,print_to_file)
        elif (different_tasks is True):  # multiple tasks
            if (current_index == 0):  # index = 0
                current_local_index = 0
                qoi,pickled_current_model,time_for_qoi = \
                    mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_local_index,current_analysis,time_for_qoi,mapping_flag,print_to_file)
            else:  # index > 0
                for current_local_index in range(current_index + 1):
                    if ((adaptive_refinement_jump_to_finest_level is False) or
                        (adaptive_refinement_jump_to_finest_level is True and
                         (current_local_index == 0
                          or current_local_index == current_index))):
                        if (mapping_flag is False):
                            qoi,pickled_current_model,time_for_qoi = \
                                mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_local_index,current_analysis,time_for_qoi,mapping_flag,print_to_file)
                        elif (mapping_flag is True):
                            qoi,pickled_current_model,time_for_qoi = \
                                mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_local_index,current_analysis,time_for_qoi,mapping_flag,print_to_file,pickled_reference_model_mapping=pickled_mapping_reference_model)
                            del (pickled_coarse_model)
                        pickled_coarse_model = pickled_current_model
                        del (pickled_current_model)
                    else:  # not running since we jump from coarsest to finest level
                        pass
        else:
            raise Exception(
                "Boolean variable different task is not a boolean, instead is equal to",
                different_tasks)
        return qoi, time_for_qoi

    """
    function executing an instance of the UQ algorithm, i.e. a single MC simulation and eventually the refinement (that occurs before the simulation run)
    input:  self: an instance of the class
    output: qoi: quantity of interest value
    """

    def executeInstanceDeterministicAdaptiveRefinement(self, random_variable):
        # local variables
        current_index = self.solverWrapperIndex[0]
        pickled_model = self.pickled_model[current_index]
        pickled_project_parameters = self.pickled_project_parameters[0]
        current_analysis = self.analysis
        time_for_qoi = 0.0
        qoi, time_for_qoi = mds.executeInstanceDeterministicAdaptiveRefinement_Wrapper(
            current_index, pickled_model, pickled_project_parameters,
            current_analysis, random_variable, time_for_qoi)
        return qoi, time_for_qoi

    """
    function executing an instance of the UQ algorithm, i.e. a single MC simulation and eventually the refinement (that occurs before the simulation run)
    input:  self: an instance of the class
    output: qoi: quantity of interest value
    """

    def executeInstanceReadingFromFile(self, random_variable):
        # local variables
        current_index = self.solverWrapperIndex[0]
        pickled_model = self.pickled_model[0]
        pickled_project_parameters = self.pickled_project_parameters[0]
        current_analysis = self.analysis
        time_for_qoi = 0.0
        # TODO - Change this to be more general
        qoi, time_for_qoi = mds.executeInstanceReadingFromFile_Wrapper(
            current_index, pickled_model, pickled_project_parameters,
            current_analysis, random_variable, time_for_qoi)
        return qoi, time_for_qoi

    ####################################################################################################
    ####################################### SERIALIZATION TOOLS ########################################
    ####################################################################################################
    """
    function serializing and pickling the model and the project parameters of the problem
    the serialization-pickling process is the following:
    i)   from Model/Parameters Kratos object to StreamSerializer Kratos object
    ii)  from StreamSerializer Kratos object to pickle string
    iii) from pickle string to StreamSerializer Kratos object
    iv)  from StreamSerializer Kratos object to Model/Parameters Kratos object
    requires: self.project_parameters_path: path of the Project Parameters file
    builds: self.pickled_model:              pickled model
            self.pickled_project_parameters: pickled project parameters
    input:  self: an instance of the class
    """

    # def SerializeModelParameters(self):
    #     with open(self.project_parameters_path,'r') as parameter_file:
    #         parameters = KratosMultiphysics.Parameters(parameter_file.read())
    #     model = KratosMultiphysics.Model()
    #     fake_sample = [1.0] # TODO: make application-indipendent
    #     simulation = self.analysis(model,parameters,fake_sample)
    #     simulation.Initialize()
    #     serialized_model = KratosMultiphysics.StreamSerializer()
    #     serialized_model.Save("ModelSerialization",simulation.model)
    #     serialized_project_parameters = KratosMultiphysics.StreamSerializer()
    #     serialized_project_parameters.Save("ParametersSerialization",simulation.project_parameters)
    #     self.serialized_model = serialized_model
    #     self.serialized_project_parameters = serialized_project_parameters
    #     # pickle dataserialized_data
    #     pickled_model = pickle.dumps(serialized_model, 2) # second argument is the protocol and is NECESSARY (according to pybind11 docs)
    #     pickled_project_parameters = pickle.dumps(serialized_project_parameters, 2)
    #     self.pickled_model = pickled_model
    #     self.pickled_project_parameters = pickled_project_parameters
    #     self.is_project_parameters_pickled = True
    #     self.is_model_pickled = True
    #     print("\n","#"*50," SERIALIZATION MODEL PART AND PARAMETERS COMPLETED ","#"*50,"\n")

    def SerializeModelParameters(self):
        self.serialized_model = []
        self.serialized_project_parameters = []
        self.pickled_model = []
        self.pickled_project_parameters = []

        if (self.refinement_strategy == "stochastic_adaptive_refinement"
                or self.refinement_strategy == "reading_from_file"):
            self.SerializeModelParametersStochasticAdaptiveRefinement()
        elif (self.refinement_strategy == "deterministic_adaptive_refinement"):
            self.SerializeModelParametersDeterministicAdaptiveRefinement()
        else:
            raise Exception(
                "Specify refinement_strategy: stochastic_adaptive_refinement or deterministic_adaptive_refinement"
            )
        self.is_project_parameters_pickled = True
        self.is_model_pickled = True
        print("\n", "#" * 50,
              " SERIALIZATION MODEL AND PROJECT PARAMETERS COMPLETED ",
              "#" * 50, "\n")

    def SerializeModelParametersStochasticAdaptiveRefinement(self):
        with open(self.project_parameters_path, 'r') as parameter_file:
            parameters = KratosMultiphysics.Parameters(parameter_file.read())
        # create wrapper instance to modify current project parameters
        self.wrapper = ParametersWrapper(parameters)
        # serialize parmeters (to avoid adding new data dependent on the application)
        parameters = self.wrapper.SetModelImportSettingsInputType(
            "use_input_model_part")
        serialized_project_parameters = KratosMultiphysics.StreamSerializer()
        serialized_project_parameters.Save("ParametersSerialization",
                                           parameters)
        self.serialized_project_parameters.append(
            serialized_project_parameters)
        # reset to read the model part
        parameters = self.wrapper.SetModelImportSettingsInputType("mdpa")
        # prepare the model to serialize
        model = KratosMultiphysics.Model()
        fake_sample = self.fake_sample_to_serialize
        simulation = self.analysis(model, parameters, fake_sample)
        simulation.Initialize()
        # reset general flags
        main_model_part_name = self.wrapper.GetModelPartName()
        simulation.model.GetModelPart(
            main_model_part_name).ProcessInfo.SetValue(
                KratosMultiphysics.IS_RESTARTED, True)
        # serialize model
        serialized_model = KratosMultiphysics.StreamSerializer()
        serialized_model.Save("ModelSerialization", simulation.model)
        self.serialized_model.append(serialized_model)
        # pickle dataserialized_data
        pickled_model = pickle.dumps(
            serialized_model, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        pickled_project_parameters = pickle.dumps(
            serialized_project_parameters, 2)
        self.pickled_model.append(pickled_model)
        self.pickled_project_parameters.append(pickled_project_parameters)

    def SerializeModelParametersDeterministicAdaptiveRefinement(self):
        self.SerializeModelParametersStochasticAdaptiveRefinement(
        )  # to prepare parameters and model part of coarsest level
        number_levels_to_serialize = self.solverWrapperIndex[0]
        # same routine of ExecuteInstanceConcurrentAdaptiveRefinemnt() to build models and parameters, but here we save models and parameters
        pickled_coarse_model = self.pickled_model[0]
        pickled_coarse_project_parameters = self.pickled_project_parameters[0]
        pickled_custom_metric_refinement_parameters = self.pickled_custom_metric_refinement_parameters
        pickled_custom_remesh_refinement_parameters = self.pickled_custom_remesh_refinement_parameters
        current_analysis = self.analysis
        # generate the sample and prepare auxiliary variables we need
        fake_sample = self.fake_sample_to_serialize
        fake_computational_time = 0.0
        if (number_levels_to_serialize > 0):
            for current_level in range(number_levels_to_serialize + 1):
                if (current_level < number_levels_to_serialize):
                    fake_qoi,pickled_current_model,fake_computational_time = \
                        mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(number_levels_to_serialize,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,fake_sample,current_level,current_analysis,fake_computational_time)
                    # mds.executeInstanceStochasticAdaptiveRefinementAux_Task(number_levels_to_serialize,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,fake_sample,current_level,current_analysis,fake_computational_time)
                    del (pickled_coarse_model)
                    pickled_coarse_model = pickled_current_model
                elif (current_level == number_levels_to_serialize):
                    pickled_current_model, fake_computational_time = mds.executeInstanceOnlyAdaptiveRefinement_Wrapper(
                        pickled_coarse_model,
                        pickled_coarse_project_parameters,
                        pickled_custom_metric_refinement_parameters,
                        pickled_custom_remesh_refinement_parameters,
                        fake_sample, current_level, current_analysis,
                        fake_computational_time)
                # save if current level > 0
                if (current_level > 0):
                    # save pickled and serialized model and parameters
                    self.pickled_model.append(pickled_current_model)
                    self.serialized_model.append(
                        pickle.loads(pickled_current_model))
                del (pickled_current_model)

    """
    function serializing and pickling the custom setting metric and remeshing for the refinement
    requires: self.custom_metric_refinement_parameters
              self.custom_remesh_refinement_parameters
    builds: self.pickled_custom_metric_refinement_parameters
            self.pickled_custom_remesh_refinement_parameters
    input:  self: an instance of the class
    """

    def SerializeRefinementParameters(self):
        metric_refinement_parameters = self.custom_metric_refinement_parameters
        remeshing_refinement_parameters = self.custom_remesh_refinement_parameters
        # save parameters as StreamSerializer Kratos objects
        serialized_metric_refinement_parameters = KratosMultiphysics.StreamSerializer(
        )
        serialized_metric_refinement_parameters.Save(
            "MetricRefinementParametersSerialization",
            metric_refinement_parameters)
        serialized_remesh_refinement_parameters = KratosMultiphysics.StreamSerializer(
        )
        serialized_remesh_refinement_parameters.Save(
            "RemeshRefinementParametersSerialization",
            remeshing_refinement_parameters)
        # pickle parameters
        pickled_metric_refinement_parameters = pickle.dumps(
            serialized_metric_refinement_parameters, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        pickled_remesh_refinement_parameters = pickle.dumps(
            serialized_remesh_refinement_parameters, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        self.pickled_custom_metric_refinement_parameters = pickled_metric_refinement_parameters
        self.pickled_custom_remesh_refinement_parameters = pickled_remesh_refinement_parameters
        self.is_custom_settings_metric_refinement_pickled = True
        self.is_custom_settings_remesh_refinement_pickled = True
        print("\n", "#" * 50,
              " SERIALIZATION REFINEMENT PARAMETERS COMPLETED ", "#" * 50,
              "\n")

    ####################################################################################################
    ######################################### AUXILIARY TOOLS ##########################################
    ####################################################################################################
    """
    function reading the refinement parameters passed from json file
    input:  self: an instance of the class
    """

    def SetRefinementParameters(self):
        with open(self.refinement_parameters_path, 'r') as parameter_file:
            parameters = KratosMultiphysics.Parameters(parameter_file.read())
        self.custom_metric_refinement_parameters = parameters["hessian_metric"]
        self.custom_remesh_refinement_parameters = parameters["refinement_mmg"]

    """
    function giving as output the mesh discretization parameter
    the mesh parameter is the reciprocal of the minimum mesh size of the grid
    input:  self: an instance of the class
    """

    def ComputeMeshParameters(self):
        # unpickle and unserialize model and build Kratos Model object
        serialized_model = pickle.loads(self.pickled_model[0])
        current_model = KratosMultiphysics.Model()
        serialized_model.Load("ModelSerialization", current_model)
        # unpickle and unserialize parameters and build Kratos Parameters object
        serialized_project_parameters = pickle.loads(
            self.pickled_project_parameters[0])
        current_project_parameters = KratosMultiphysics.Parameters()
        serialized_project_parameters.Load("ParametersSerialization",
                                           current_project_parameters)
        # unpickle and unserialize metric refinement parameters and build Kratos Parameters objects
        serialized_custom_metric_refinement_parameters = pickle.loads(
            self.pickled_custom_metric_refinement_parameters)
        current_custom_metric_refinement_parameters = KratosMultiphysics.Parameters(
        )
        serialized_custom_metric_refinement_parameters.Load(
            "MetricRefinementParametersSerialization",
            current_custom_metric_refinement_parameters)
        self.mesh_sizes = []
        self.mesh_parameters = []
        level = self.solverWrapperIndex[0]
        adaptive_refinement_manager = AdaptiveRefinement(
            level, current_model, current_project_parameters,
            current_custom_metric_refinement_parameters, None)
        adaptive_refinement_manager.EstimateMeshSizeCurrentLevel()
        h_current_level = adaptive_refinement_manager.mesh_size
        mesh_parameter_current_level = h_current_level**(-1)
        self.mesh_sizes.append(h_current_level)
        self.mesh_parameters.append(mesh_parameter_current_level)
Example #6
0
class KratosSolverWrapper(sw.SolverWrapper):
    """
    Solver wrapper class managing Kratos Multiphysics (Kratos) solver.

    Attributes:
    - analysis: Kratos analysis stage. The analysis stage class default name is SimulationScenario. The default file name is simulation_scenario. It is imported with the "analysisStage" key. By default an example analysis stage is called.
    - adaptive_refinement_jump_to_finest_level: boolean. Used in multilevel algorithms when "stochastic_adaptive_refinement" strategy is selected. If true, intermediate refinement indices are skipped. Set by adaptiveRefinementJumpToFinestLevel key.
    - asynchronous: boolean. If true, the asynchronous algorithm should be run. If false, the standard synchronous algorithms should be run. Set by asynchronous key.
    - different_tasks: boolean. Used in multilevel algorithms when "stochastic_adaptive_refinement" strategy is selected. If true, different indices are run all together in the same task. If false, each index is run in a different task. Set by not TaskAllAtOnce key.
    - fake_sample_to_serialize: list. A variable which is used just to serialize the Kratos Model and the Kratos Parameters. The list should be of the same type of the random variable generated by the generator. Set by fakeRandomVariable key.
    - is_mpi: boolean stating if problem is run in MPI or in serial.
    - mapping_output_quantities: boolean. If true, the analysis stage is prepared to map the variables of interest to a reference Kratos Model. By default, such Kratos Model is the coarsest index. Set by mappingOutputQuantities key.
    - number_contributions_per_instance: integer. Defines the number of realization per each solve call. Useful if one wants to exploit ensemble average, together with hierarchical Monte Carlo methods. Set by numberContributionsPerInstance key.
    - outputBatchSize: integer. Defines the size of each sub-list of the Quantities of Interest list which is returned by the solve method. It is alternative to outputDimension, defined below. Set by OutputBatchSize.
    - outputDimension: integer or list of integers. If integer, equals to len(sample), where sample is the first output argument of self.solve(). If list of integers, then it means that samples are split in future lists, and outputDimension is [len(subSample) for subSample in sample]. Set by OutputDimension key.
    - print_to_file: boolean. If true, prepares the distributed environment programing model PyCOMPSs to write a file inside the solve task. Set by printToFile key.
    - project_parameters_path: string or list of strings. Defines the path to Kratos Project Parameters. Set by projectParametersPath key.
    - qoi_estimator: list of strings. Each string is the moment estimator name to which each quantity of interest is associated.
    - refinement_parameters_path: string. Define the path to the Kratos Adaptive Refinement Project Parameters. Set by refinementParametersPath key.
    - refinement_strategy: string. Options are: "reading_from_file", "deterministic_adaptive_refinement", "stochastic_adaptive_refinement". It defines the refinement strategy for multilevel algorithms. Set by refinementStrategy key.
    - size_multi_x_moment_estimator: integer. Defines the size of each vector quantity if interest. If integer, vector quantities of interest have the same size. It is required to set a priori this value only because of returnZeroQoiAndTime_Task, which needs to know how many 0s to return. Set by sizeMultiXMomentEstimator. Obs: in future, also a list will be supported. If list, the list has the same length of numberMultiMomentEstimator+numberMultiCombinedMomentEstimator.

    Methods:
    - serialize: method serializing Kratos Model and Kratos Parameters.
    - solve: method running the problem.
    Other methods are called from the two methods defined above.
    """

    # TODO: are both outputBatchSize and outputBatchSize needed? Probably not.
    def __init__(self, **keywordArgs):
        super().__init__(**keywordArgs)
        self.analysis = dynamicImport(
            keywordArgs.get(
                "analysisStage",
                ("xmc.classDefs_solverWrapper.methodDefs_KratosSolverWrapper"
                 ".simulation_definition.SimulationScenario")))
        self.adaptive_refinement_jump_to_finest_level = keywordArgs.get(
            "adaptiveRefinementJumpToFinestLevel", False)
        self.asynchronous = keywordArgs.get("asynchronous", False)
        self.different_tasks = not keywordArgs.get("taskAllAtOnce", True)
        self.fake_sample_to_serialize = keywordArgs.get("fakeRandomVariable")
        self.mapping_output_quantities = keywordArgs.get(
            "mappingOutputQuantities", False)
        self.is_mpi = keywordArgs.get("isMpi", False)
        self.number_contributions_per_instance = keywordArgs.get(
            "numberContributionsPerInstance", 1)
        self.outputBatchSize = keywordArgs.get("outputBatchSize", 1)
        self.print_to_file = keywordArgs.get("printToFile", False)
        self.project_parameters_path = keywordArgs.get("projectParametersPath")
        self.qoi_estimator = keywordArgs.get("qoiEstimator")
        self.refinement_parameters_path = keywordArgs.get(
            "refinementParametersPath")
        self.refinement_strategy = keywordArgs.get("refinementStrategy")
        self.size_multi_x_moment_estimator = keywordArgs.get(
            "sizeMultiXMomentEstimator",
            -1)  # remove after returnZeroQoiAndTime_Task is removed

        # Set outputDimension
        self.outputDimension = keywordArgs.get("outputDimension", None)
        # If not given, compute from self.outputBatchSize for backward compatibility
        if self.outputDimension is None:
            outputNb = self._numberOfOutputs()
            # Total number of output splits, including (possibly) a last one of smaller size
            batchNb = int(math.ceil(outputNb / self.outputBatchSize))
            # Assemble the list of sizes of each split
            # They are all equal to outputBatchSize, except perhaps the last one
            # E.g. outputBatchSize=2 and outputNb=5 gives [2,2,1]
            self.outputDimension = [
                min(self.outputBatchSize, outputNb - i * self.outputBatchSize)
                for i in range(batchNb)
            ]

        # workaround for Monte Carlo
        if (self.solverWrapperIndex == []):
            self.solverWrapperIndex.append(0)

        if (self.solverWrapperIndex[0] >= 0):  # for index < 0 not needed
            if (self.asynchronous is not True):  # synchronous framework
                self.serialize()
            else:  # asynchronous framework
                pass

    def serialize(self):
        """
        Method serializing Kratos Model and Kratos Parameters.

        Inputs:
        - self: an instance of the class.
        """

        if self.refinement_strategy not in [
                "stochastic_adaptive_refinement",
                "deterministic_adaptive_refinement", "reading_from_file"
        ]:
            raise Exception(
                "Select KratosMultiphysics refinement stategy.\nOptions:\
                \n   i) stochastic_adaptive_refinement\
                \n  ii) deterministic_adaptive_refinement\
                \n iii) reading_from_file")
        self.is_project_parameters_pickled = False
        self.is_model_pickled = False
        self.is_custom_settings_metric_refinement_pickled = False
        self.is_custom_settings_remesh_refinement_pickled = False
        self.SetRefinementParameters()
        self.SerializeRefinementParameters()
        self.SerializeModelParameters()
        print(self.__class__.__name__,
              ": Model and parameters serialized correctly.")

    def solve(self, random_variable):
        """
        Method running the problem.

        Inputs:
        - self: an instance of the class.
        - random_variable: random event in the form of list.

        Outputs:
        - qoi_list: list of structure respecting self.outputDimension. It contains the quantities of interest.
        - time_for_qoi: float. Measure of time to generate the sample.
        """

        if all([component >= 0 for component in self.solverWrapperIndex]):
            aux_qoi_array = []
            # loop over contributions (by default only one)
            for contribution_counter in range(
                    0, self.number_contributions_per_instance):
                self.current_local_contribution = contribution_counter
                # if multiple ensembles, append a seed to the random variable list
                # for example, this seed is used to generate different initial conditions
                if self.number_contributions_per_instance > 1:
                    random_variable.append(int(np.random.uniform(0,
                                                                 429496729)))
                # solve
                if (self.refinement_strategy ==
                        "stochastic_adaptive_refinement"):
                    qoi, time_for_qoi = self.executeInstanceStochasticAdaptiveRefinement(
                        random_variable)
                elif (self.refinement_strategy ==
                      "deterministic_adaptive_refinement"):
                    qoi, time_for_qoi = self.executeInstanceDeterministicAdaptiveRefinement(
                        random_variable)
                elif (self.refinement_strategy == "reading_from_file"):
                    qoi, time_for_qoi = self.executeInstanceReadingFromFile(
                        random_variable)
                # append components to aux array
                aux_qoi_array.append(qoi)
            # delete COMPSs future objects no longer needed
            delete_object(random_variable)

            # postprocess components
            if self.number_contributions_per_instance > 1:
                unm = mdu.UnfolderManager(self._numberOfOutputs(),
                                          self.outputBatchSize)
                if (self._numberOfOutputs() == self.outputBatchSize):
                    qoi_list = [
                        unm.PostprocessContributionsPerInstance_Task(
                            aux_qoi_array,
                            self.qoi_estimator,
                            returns=math.ceil(self._numberOfOutputs() /
                                              self.outputBatchSize))
                    ]
                elif (self._numberOfOutputs() > self.outputBatchSize):
                    qoi_list = unm.PostprocessContributionsPerInstance_Task(
                        aux_qoi_array,
                        self.qoi_estimator,
                        returns=math.ceil(self._numberOfOutputs() /
                                          self.outputBatchSize))
                else:
                    raise Exception(
                        "_numberOfOutputs() returns a value smaller than self.outputBatchSize. Set outputBatchSize smaller or equal to the number of scalar outputs."
                    )
                delete_object(unm)
            else:
                # unfold qoi into its components of fixed size
                unm = mdu.UnfolderManager(self._numberOfOutputs(),
                                          self.outputBatchSize)
                if (self._numberOfOutputs() == self.outputBatchSize):
                    qoi_list = [
                        unm.UnfoldNValues_Task(
                            aux_qoi_array[0],
                            returns=math.ceil(self._numberOfOutputs() /
                                              self.outputBatchSize))
                    ]
                elif (self._numberOfOutputs() > self.outputBatchSize):
                    qoi_list = unm.UnfoldNValues_Task(
                        aux_qoi_array[0],
                        returns=math.ceil(self._numberOfOutputs() /
                                          self.outputBatchSize))
                else:
                    raise Exception(
                        "_numberOfOutputs() returns a value smaller than self.outputBatchSize. Set outputBatchSize smaller or equal to the number of scalar outputs."
                    )
                # delete COMPSs future objects no longer needed
                delete_object(unm)

            # delete COMPSs future objects no longer needed
            for contribution_counter in range(
                    0, self.number_contributions_per_instance):
                delete_object(aux_qoi_array[contribution_counter])
            #delete_object(qoi)
            del (aux_qoi_array)

        else:
            qoi, time_for_qoi = mds.returnZeroQoiAndTime_Task(
                self.qoi_estimator, self.size_multi_x_moment_estimator)
            # unfold qoi into its components of fixed size
            unm = mdu.UnfolderManager(self._numberOfOutputs(),
                                      self.outputBatchSize)
            if (self._numberOfOutputs() == self.outputBatchSize):
                qoi_list = [
                    unm.UnfoldNValues_Task(
                        qoi,
                        returns=math.ceil(self._numberOfOutputs() /
                                          self.outputBatchSize))
                ]
            elif (self._numberOfOutputs() > self.outputBatchSize):
                qoi_list = unm.UnfoldNValues_Task(
                    qoi,
                    returns=math.ceil(self._numberOfOutputs() /
                                      self.outputBatchSize))
            else:
                raise Exception(
                    "_numberOfOutputs() returns a value smaller than self.outputBatchSize. Set outputBatchSize smaller or equal to the number of scalar outputs."
                )
            # delete COMPSs future objects no longer needed
            delete_object(unm)
            delete_object(qoi)

        return qoi_list, time_for_qoi

    ####################################################################################################
    ######################################### EXECUTION TOOLS ##########################################
    ####################################################################################################

    def executeInstanceStochasticAdaptiveRefinement(self, random_variable):
        """
        Method executing an instance of the UQ algorithm, i.e. a single MC realization and eventually the refinement (that occurs before the simulation run). To be called if the selected refinement strategy is stochastic_adaptive_refinement.

        Inputs:
        - self: an instance of the class.

        Outputs:
        - qoi: list. It contains the quantities of interest.
        - time_for_qoi: float. Measure of time to generate the sample.
        """

        # local variables
        current_index = self.solverWrapperIndex[0]
        pickled_coarse_model = self.pickled_model[0]
        pickled_reference_model_mapping = pickled_coarse_model
        pickled_coarse_project_parameters = self.pickled_project_parameters[0]
        pickled_custom_metric_refinement_parameters = self.pickled_custom_metric_refinement_parameters
        pickled_custom_remesh_refinement_parameters = self.pickled_custom_remesh_refinement_parameters
        current_analysis = self.analysis
        different_tasks = self.different_tasks
        mapping_flag = self.mapping_output_quantities
        adaptive_refinement_jump_to_finest_level = self.adaptive_refinement_jump_to_finest_level
        print_to_file = self.print_to_file
        current_local_contribution = self.current_local_contribution
        time_for_qoi = 0.0
        if (different_tasks is False):  # single task
            if self.is_mpi:
                qoi,time_for_qoi = \
                    mpi_mds.executeInstanceStochasticAdaptiveRefinementAllAtOnce_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_analysis,time_for_qoi,mapping_flag,adaptive_refinement_jump_to_finest_level,print_to_file,current_local_contribution)
            else:
                qoi,time_for_qoi = \
                    mds.executeInstanceStochasticAdaptiveRefinementAllAtOnce_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_analysis,time_for_qoi,mapping_flag,adaptive_refinement_jump_to_finest_level,print_to_file,current_local_contribution)
        elif (different_tasks is True):  # multiple tasks
            if (current_index == 0):  # index = 0
                current_local_index = 0
                if self.is_mpi:
                    qoi,pickled_current_model,time_for_qoi = \
                        mpi_mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_local_index,current_analysis,time_for_qoi,mapping_flag,print_to_file,current_local_contribution)
                else:
                    qoi,pickled_current_model,time_for_qoi = \
                        mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_local_index,current_analysis,time_for_qoi,mapping_flag,print_to_file,current_local_contribution)
                delete_object(pickled_current_model)
            else:  # index > 0
                for current_local_index in range(current_index + 1):
                    if ((adaptive_refinement_jump_to_finest_level is False) or
                        (adaptive_refinement_jump_to_finest_level is True and
                         (current_local_index == 0
                          or current_local_index == current_index))):
                        if (mapping_flag is False):
                            qoi,pickled_current_model,time_for_qoi = \
                                mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_local_index,current_analysis,time_for_qoi,mapping_flag,print_to_file,current_local_contribution)
                        elif (mapping_flag is True):
                            qoi,pickled_current_model,time_for_qoi = \
                                mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(current_index,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,random_variable,current_local_index,current_analysis,time_for_qoi,mapping_flag,print_to_file,current_local_contribution,pickled_mapping_reference_model=pickled_reference_model_mapping)
                            delete_object(pickled_coarse_model)
                            del (pickled_coarse_model)
                        pickled_coarse_model = pickled_current_model
                        del (pickled_current_model)
                    else:  # not running since we jump from coarsest to finest level
                        pass
                delete_object(pickled_coarse_model)
        else:
            raise Exception(
                "Boolean variable different task is not a boolean, instead is equal to",
                different_tasks)
        return qoi, time_for_qoi

    def executeInstanceDeterministicAdaptiveRefinement(self, random_variable):
        """
        Method executing an instance of the UQ algorithm, i.e. a single MC realization and eventually the refinement (that occurs before the simulation run). To be called if the selected refinement strategy is deterministic_adaptive_refinement.

        Inputs:
        - self: an instance of the class.

        Outputs:
        - qoi: list. It contains the quantities of interest.
        - time_for_qoi: float. Measure of time to generate the sample.
        """

        # local variables
        current_index = self.solverWrapperIndex[0]
        pickled_model = self.pickled_model[current_index]
        pickled_mapping_reference_model = self.pickled_model[0]
        pickled_project_parameters = self.pickled_project_parameters[
            current_index]
        mapping_flag = self.mapping_output_quantities
        print_to_file = self.print_to_file
        current_local_contribution = self.current_local_contribution
        current_analysis = self.analysis
        time_for_qoi = 0.0
        if self.is_mpi:
            qoi, time_for_qoi = mpi_mds.executeInstanceDeterministicAdaptiveRefinement_Wrapper(
                current_index, pickled_model, pickled_project_parameters,
                current_analysis, random_variable, time_for_qoi, mapping_flag,
                pickled_mapping_reference_model, print_to_file,
                current_local_contribution)
        else:
            qoi, time_for_qoi = mds.executeInstanceDeterministicAdaptiveRefinement_Wrapper(
                current_index, pickled_model, pickled_project_parameters,
                current_analysis, random_variable, time_for_qoi, mapping_flag,
                pickled_mapping_reference_model, print_to_file,
                current_local_contribution)
        return qoi, time_for_qoi

    def executeInstanceReadingFromFile(self, random_variable):
        """
        Method executing an instance of the UQ algorithm, i.e. a single MC realization and eventually the refinement (that occurs before the simulation run). To be called if the selected refinement strategy is reading_from_file.

        Inputs:
        - self: an instance of the class.

        Outputs:
        - qoi: list. It contains the quantities of interest.
        - time_for_qoi: float. Measure of time to generate the sample.
        """

        # local variables
        current_index = self.solverWrapperIndex[0]
        pickled_model = self.pickled_model[current_index]
        pickled_mapping_reference_model = self.pickled_mapping_reference_model[
            current_index]
        pickled_project_parameters = self.pickled_project_parameters[
            current_index]
        mapping_flag = self.mapping_output_quantities
        print_to_file = self.print_to_file
        current_local_contribution = self.current_local_contribution
        current_analysis = self.analysis
        time_for_qoi = 0.0
        if self.is_mpi:
            qoi, time_for_qoi = mpi_mds.executeInstanceReadingFromFile_Wrapper(
                current_index, pickled_model, pickled_project_parameters,
                current_analysis, random_variable, time_for_qoi, mapping_flag,
                pickled_mapping_reference_model, print_to_file,
                current_local_contribution)
        else:
            qoi, time_for_qoi = mds.executeInstanceReadingFromFile_Wrapper(
                current_index, pickled_model, pickled_project_parameters,
                current_analysis, random_variable, time_for_qoi, mapping_flag,
                pickled_mapping_reference_model, print_to_file,
                current_local_contribution)
        return qoi, time_for_qoi

    ####################################################################################################
    ####################################### SERIALIZATION TOOLS ########################################
    ####################################################################################################

    def SerializeModelParameters(self):
        """
        Method managing the serialization and pickling of the Kratos Model and the Kratos Parameters of the problem. It builds self.pickled_model and self.pickled_project_parameters.
        The serialization-pickling process is the following:
        i)   from Model/Parameters Kratos object to MpiSerializer Kratos object,
        ii)  from MpiSerializer Kratos object to pickle string,
        iii) from pickle string to MpiSerializer Kratos object,
        iv)  from MpiSerializer Kratos object to Model/Parameters Kratos object.
        Depending on the refinement strategy, three different methods may be called and are defined next.
        We remark that creating the class member pickled_mapping_reference_model is required by MPI runs,
        since we need such coarse model to be serialized with the same number of processors of the MPI task.

        Inputs:
        - self: an instance of the class.
        """

        self.serialized_model = []
        self.serialized_project_parameters = []
        self.pickled_model = []
        self.pickled_project_parameters = []
        self.pickled_mapping_reference_model = []

        if (self.refinement_strategy == "stochastic_adaptive_refinement"):
            self.SerializeModelParametersStochasticAdaptiveRefinement()
        elif (self.refinement_strategy == "deterministic_adaptive_refinement"):
            self.SerializeModelParametersDeterministicAdaptiveRefinement()
        elif (self.refinement_strategy == "reading_from_file"):
            self.SerializeModelParametersReadingFromFile()
        else:
            raise Exception(
                "Specify refinement_strategy: stochastic_adaptive_refinement or deterministic_adaptive_refinement or reading_from_file"
            )
        self.is_project_parameters_pickled = True
        self.is_model_pickled = True

    def SerializeSerialModel(self, parameters):
        """
        Method serializing and pickling the Kratos Model and the Kratos Parameters of the problem. It builds self.pickled_model and self.pickled_project_parameters. It is called if we are not runnnig in MPI.

        Inputs:
        - self: an instance of the class.
        - parameters: KratosMultiphysics.ProjectParameters object. It contains the settings of the simulation.
        """
        # prepare the model to serialize
        model = KratosMultiphysics.Model()
        fake_sample = self.fake_sample_to_serialize
        simulation = self.analysis(model, parameters, fake_sample)
        simulation.Initialize()
        # reset general flags
        main_model_part_name = self.wrapper.GetModelPartName()
        simulation.model.GetModelPart(
            main_model_part_name).ProcessInfo.SetValue(
                KratosMultiphysics.IS_RESTARTED, True)
        # serialize model
        serialized_model = KratosMultiphysics.MpiSerializer()
        serialized_model.Save("ModelSerialization", simulation.model)
        self.serialized_model.append(serialized_model)
        # pickle dataserialized_data
        pickled_model = pickle.dumps(
            serialized_model, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        return pickled_model

    def SerializeModelParametersStochasticAdaptiveRefinement(self):
        """
        Method serializing and pickling the Kratos Model and the Kratos Parameters of the problem. It builds self.pickled_model and self.pickled_project_parameters. To be called if the selected refinement strategy is stochastic_adaptive_refinement.

        Inputs:
        - self: an instance of the class.
        """

        with open(self.project_parameters_path, "r") as parameter_file:
            parameters = KratosMultiphysics.Parameters(parameter_file.read())
        # create wrapper instance to modify current project parameters
        self.wrapper = ParametersWrapper(parameters)
        # serialize and pickle parmeters to serialize the model in MPI
        self.wrapper.SetModelImportSettingsInputType("use_input_model_part")
        serialized_project_parameters_tmp = KratosMultiphysics.MpiSerializer()
        serialized_project_parameters_tmp.Save("ParametersSerialization",
                                               parameters)
        pickled_project_parameters_tmp = pickle.dumps(
            serialized_project_parameters_tmp, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)

        # remove the materials filename to pickle the parameters
        # this is required to read the materials only once
        # finally, we restore the materials filename to read the materials
        # in the model serialization
        # it is important to serialize first the parameters and then the model
        # to avoid additional data which may be added to the parameters
        # remove materials filename from Kratos settings and revert model part type
        materials_filename = self.wrapper.GetMaterialsFilename()
        self.wrapper.SetMaterialsFilename("")
        # serialize and pickle Kratos project parameters
        serialized_project_parameters = KratosMultiphysics.MpiSerializer()
        serialized_project_parameters.Save("ParametersSerialization",
                                           parameters)
        pickled_project_parameters = pickle.dumps(
            serialized_project_parameters, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        # append to attributes
        self.serialized_project_parameters.append(
            serialized_project_parameters)
        self.pickled_project_parameters.append(pickled_project_parameters)

        # reset to read the model part and materials filename
        self.wrapper.SetModelImportSettingsInputType("mdpa")
        self.wrapper.SetMaterialsFilename(materials_filename)

        # pickle and eventually serialize model
        if self.is_mpi:
            if not parameters["problem_data"]["parallel_type"].GetString(
            ) == "MPI":
                raise (Exception("XMC is set in MPI but Kratos is not!"))
            # self.serialized_model cannot be retrieved in MPI so only pickled model is returned
            # returns: [[model1_1,model_1_2, model_1_3, model_1_4], [model2_1,model_2_2, model_2_3, model_2_4]]
            # we need to pass the index to serialize with the correct number of MPI processors
            # however, since pickled_project_parameters is the same across levels, the same model part is considered
            current_index = self.solverWrapperIndex[0]
            pickled_model = mpi_mds.SerializeMPIModel_Wrapper( \
                pickled_project_parameters_tmp, self.wrapper.GetModelPartName(), self.fake_sample_to_serialize, self.analysis, current_index=current_index)
        else:
            if parameters["problem_data"]["parallel_type"].GetString(
            ) == "MPI":
                raise (Exception("Kratos is set in MPI but XMC is not!"))
            pickled_model = self.SerializeSerialModel(parameters)
        # append to attribute
        self.pickled_model.append(pickled_model)

        # remove temporary objects created for MPI serialization
        del (serialized_project_parameters_tmp)
        del (pickled_project_parameters_tmp)

    def SerializeModelParametersDeterministicAdaptiveRefinement(self):
        """
        Method serializing and pickling the Kratos Model and the Kratos Parameters of the problem. It builds self.pickled_model and self.pickled_project_parameters. To be called if the selected refinement strategy is deterministic_adaptive_refinement.

        Inputs:
        - self: an instance of the class.
        """

        # Serialize model and parameters of coarsest level (level = 0).
        # If we are running with MPI parallel type,
        # the model is being serialized in a MPI task
        # with the same number of processes required by level = self.solverWrapperIndex[0].
        # This strategy works in both cases the solverWrapper instance is solving level 0
        # or if it is solving levels > 0.
        self.SerializeModelParametersStochasticAdaptiveRefinement()
        # now serialize levels > 0
        number_levels_to_serialize = self.solverWrapperIndex[0]
        # same routine of executeInstanceStochasticAdaptiveRefinement() to build models and parameters, but here we save models and parameters
        pickled_coarse_model = self.pickled_model[0]
        pickled_coarse_project_parameters = self.pickled_project_parameters[0]
        pickled_custom_metric_refinement_parameters = self.pickled_custom_metric_refinement_parameters
        pickled_custom_remesh_refinement_parameters = self.pickled_custom_remesh_refinement_parameters
        current_analysis = self.analysis
        # generate the sample and prepare auxiliary variables we need
        fake_sample = self.fake_sample_to_serialize
        fake_computational_time = 0.0
        if (number_levels_to_serialize > 0):
            for current_level in range(number_levels_to_serialize + 1):
                if not self.is_mpi:  # serial
                    fake_qoi,pickled_current_model,fake_computational_time = \
                        mds.executeInstanceStochasticAdaptiveRefinementMultipleTasks_Wrapper(number_levels_to_serialize,pickled_coarse_model,pickled_coarse_project_parameters,pickled_custom_metric_refinement_parameters,pickled_custom_remesh_refinement_parameters,fake_sample,current_level,current_analysis,fake_computational_time,mapping_flag=False,print_to_file=False,current_contribution=0)
                elif self.is_mpi and current_level == number_levels_to_serialize:  # MPI and we serialize level of interest
                    adaptive_refinement_jump_to_finest_level = self.adaptive_refinement_jump_to_finest_level
                    pickled_current_model = mpi_mds.SerializeDeterministicAdaptiveRefinementMPIModel_Wrapper(
                        current_level, pickled_coarse_model,
                        pickled_coarse_project_parameters,
                        pickled_custom_metric_refinement_parameters,
                        pickled_custom_remesh_refinement_parameters,
                        fake_sample, current_analysis, fake_computational_time,
                        adaptive_refinement_jump_to_finest_level)
                else:  # MPI parallel type and we do not serialize since it is not the level of interest
                    # we set pickled model equal to coarsest model as workaround
                    pickled_current_model = pickled_coarse_model
                del (pickled_coarse_model)
                pickled_coarse_model = pickled_current_model
                # save if current level > 0 (level = 0 has already been saved)
                if (current_level > 0):
                    # save pickled and serialized model and parameters
                    self.pickled_model.append(pickled_current_model)
                    # self.serialized_model.append(pickle.loads(get_value_from_remote(pickled_current_model))) # commented since gives problem when solving with PyCOMPSs
                    self.pickled_project_parameters.append(
                        pickled_coarse_project_parameters)
                    # self.serialized_project_parameters.append(pickle.loads(get_value_from_remote(pickled_coarse_project_parameters))) # commented since gives problem when solving with PyCOMPSs
                del (pickled_current_model)

    def SerializeModelParametersReadingFromFile(self):
        """
        Method serializing and pickling the Kratos Model and the Kratos Parameters of the problem. It builds self.pickled_model and self.pickled_project_parameters. To be called if the selected refinement strategy is reading_from_file.

        Inputs:
        - self: an instance of the class.
        """

        current_index = 0
        for parameters_path in self.project_parameters_path:
            with open(parameters_path, "r") as parameter_file:
                parameters = KratosMultiphysics.Parameters(
                    parameter_file.read())
            # create wrapper instance to modify current project parameters
            self.wrapper = ParametersWrapper(parameters)
            # serialize and pickle parmeters to serialize the model in MPI
            # it is not required to remove the materials, since the Kratos variable
            # IS_RESTARTED is set to True
            self.wrapper.SetModelImportSettingsInputType(
                "use_input_model_part")
            serialized_project_parameters = KratosMultiphysics.MpiSerializer()
            serialized_project_parameters.Save("ParametersSerialization",
                                               parameters)
            # append to attributes
            self.serialized_project_parameters.append(
                serialized_project_parameters)
            pickled_project_parameters = pickle.dumps(
                serialized_project_parameters, 2
            )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
            self.pickled_project_parameters.append(pickled_project_parameters)
            # reset to read the model part
            self.wrapper.SetModelImportSettingsInputType("mdpa")

            # pickle and eventually serialize model
            if self.is_mpi:
                if not parameters["problem_data"]["parallel_type"].GetString(
                ) == "MPI":
                    raise (Exception("XMC is set in MPI but Kratos is not!"))
                # self.serialized_model cannot be retrieved in MPI so only pickled model is returned
                # returns: [[model1_1,model_1_2, model_1_3, model_1_4], [model2_1,model_2_2, model_2_3, model_2_4]]
                pickled_model = mpi_mds.SerializeMPIModel_Wrapper( \
                    pickled_project_parameters, self.wrapper.GetModelPartName(), self.fake_sample_to_serialize, self.analysis, current_index)
                pickled_mapping_reference_model = mpi_mds.SerializeMPIModel_Wrapper( \
                    self.pickled_project_parameters[0], self.wrapper.GetModelPartName(), self.fake_sample_to_serialize, self.analysis, current_index)
                current_index += 1
            else:
                if parameters["problem_data"]["parallel_type"].GetString(
                ) == "MPI":
                    raise (Exception("Kratos is set in MPI but XMC is not!"))
                pickled_model = self.SerializeSerialModel(parameters)
            # append to attribute
            self.pickled_model.append(pickled_model)
            if self.is_mpi:
                self.pickled_mapping_reference_model.append(
                    pickled_mapping_reference_model)
            else:
                self.pickled_mapping_reference_model.append(
                    self.pickled_model[0])

    def SerializeRefinementParameters(self):
        """
        Method serializing and pickling the custom setting metric and remeshing for the adaptive refinement. It requires self.custom_metric_refinement_parameters and self.custom_remesh_refinement_parameters. It builds self.pickled_custom_metric_refinement_parameters and self.pickled_custom_remesh_refinement_parameters.

        Inputs:
        - self: an instance of the class.
        """

        metric_refinement_parameters = self.custom_metric_refinement_parameters
        remeshing_refinement_parameters = self.custom_remesh_refinement_parameters
        # save parameters as MpiSerializer Kratos objects
        serialized_metric_refinement_parameters = KratosMultiphysics.MpiSerializer(
        )
        serialized_metric_refinement_parameters.Save(
            "MetricRefinementParametersSerialization",
            metric_refinement_parameters)
        serialized_remesh_refinement_parameters = KratosMultiphysics.MpiSerializer(
        )
        serialized_remesh_refinement_parameters.Save(
            "RemeshRefinementParametersSerialization",
            remeshing_refinement_parameters)
        # pickle parameters
        pickled_metric_refinement_parameters = pickle.dumps(
            serialized_metric_refinement_parameters, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        pickled_remesh_refinement_parameters = pickle.dumps(
            serialized_remesh_refinement_parameters, 2
        )  # second argument is the protocol and is NECESSARY (according to pybind11 docs)
        self.pickled_custom_metric_refinement_parameters = pickled_metric_refinement_parameters
        self.pickled_custom_remesh_refinement_parameters = pickled_remesh_refinement_parameters
        self.is_custom_settings_metric_refinement_pickled = True
        self.is_custom_settings_remesh_refinement_pickled = True

    ####################################################################################################
    ######################################### AUXILIARY TOOLS ##########################################
    ####################################################################################################

    def SetRefinementParameters(self):
        """
        Method reading the refinement parameters passed from json file.

        Inputs:
        - self: an instance of the class.
        """

        with open(self.refinement_parameters_path, "r") as parameter_file:
            parameters = KratosMultiphysics.Parameters(parameter_file.read())
        if parameters.Has("metric"):
            self.custom_metric_refinement_parameters = parameters["metric"]
        elif parameters.Has("hessian_metric"):
            self.custom_metric_refinement_parameters = parameters[
                "hessian_metric"]
            warnings.warn(
                ("The metric settings are passed through the \"hessian_metric\" key."
                 " This is deprecated and will be removed soon."
                 " Instead, you should pass the metric settings using the \"metric\" key."
                 ),
                FutureWarning,
            )
        else:
            raise Exception(
                "Refinement parameters, set by refinement_parameters_path, does not contain the required key \"metric\"."
            )
        if parameters.Has("remeshing"):
            self.custom_remesh_refinement_parameters = parameters["remeshing"]
        elif parameters.Has("refinement_mmg"):
            self.custom_remesh_refinement_parameters = parameters[
                "refinement_mmg"]
            warnings.warn(
                ("The remeshing settings are passed through the \"refinement_mmg\" key."
                 " This is deprecated and will be removed soon."
                 " Instead, you should pass the metric settings using the \"remeshing\" key."
                 ),
                FutureWarning,
            )
        else:
            raise Exception(
                "Refinement parameters, set by refinement_parameters_path, does not contain the required key \"remeshing\"."
            )

    def ComputeMeshParameters(self):
        """
        Method computing the mesh discretization parameter self.mesh_parameters and the mesh sizes self.mesh_sizes. The mesh parameter is the reciprocal of the minimum mesh size of the grid.

        Inputs:
        - self: an instance of the class.
        """

        # unpickle and unserialize model and build Kratos Model object
        serialized_model = pickle.loads(self.pickled_model[0])
        current_model = KratosMultiphysics.Model()
        serialized_model.Load("ModelSerialization", current_model)
        # unpickle and unserialize parameters and build Kratos Parameters object
        serialized_project_parameters = pickle.loads(
            self.pickled_project_parameters[0])
        current_project_parameters = KratosMultiphysics.Parameters()
        serialized_project_parameters.Load("ParametersSerialization",
                                           current_project_parameters)
        # unpickle and unserialize metric refinement parameters and build Kratos Parameters objects
        serialized_custom_metric_refinement_parameters = pickle.loads(
            self.pickled_custom_metric_refinement_parameters)
        current_custom_metric_refinement_parameters = KratosMultiphysics.Parameters(
        )
        serialized_custom_metric_refinement_parameters.Load(
            "MetricRefinementParametersSerialization",
            current_custom_metric_refinement_parameters)
        self.mesh_sizes = []
        self.mesh_parameters = []
        level = self.solverWrapperIndex[0]
        adaptive_refinement_manager = AdaptiveRefinement(
            level, current_model, current_project_parameters,
            current_custom_metric_refinement_parameters, None)
        adaptive_refinement_manager.EstimateMeshSizeCurrentLevel()
        h_current_level = adaptive_refinement_manager.mesh_size
        mesh_parameter_current_level = h_current_level**(-1)
        self.mesh_sizes.append(h_current_level)
        self.mesh_parameters.append(mesh_parameter_current_level)

    def _numberOfOutputs(self):
        """
        Internal method returning the total number of outputs, regardless of how
        how many members vector quantities of interest have.

        Inputs:
        - self: an instance of the class.
        """
        return len(self.qoi_estimator)