Exemple #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)
        self.wrapper.SetModelImportSettingsInputType("use_input_model_part")
        materials_filename = self.wrapper.GetMaterialsFilename()
        self.wrapper.SetMaterialsFilename("")
        serialized_project_parameters = KratosMultiphysics.StreamSerializer()
        serialized_project_parameters.Save("ParametersSerialization",parameters)
        self.serialized_project_parameters = serialized_project_parameters
        # reset to read the model part
        self.wrapper.SetModelImportSettingsInputType("mdpa")
        self.wrapper.SetMaterialsFilename(materials_filename)

        # 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
Exemple #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. 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)