diff --git a/mri/run_mri.py b/mri/run_mri.py index 086fbe2c8453de6dd5a318b9fb5f72109abaf96d..c9e7251b38c0f52c056dfa18e02388c75973e7d3 100755 --- a/mri/run_mri.py +++ b/mri/run_mri.py @@ -2,60 +2,79 @@ from __future__ import division, print_function -import os -import sys + import numpy as np -import argparse +import multiprocessing as mp # add location where to find the forward model wrapper +#import sys +#import os #sys.path.append(os.path.abspath("./")) import pymsbern - -# the forward model -def run_mri(random_params): - paramnames = sorted(["MRSignal.T1TissuePreContrast", "InflowCurve.a", "InflowCurve.b", "InflowCurve.tp", - "VesselWall.DiffusionCoefficient", "MRSignal.KappaB", "MRSignal.KappaT"]) +def run_mri(x): + """ + This function runs the forward model with the given parameters x. + + Parameters + ---------- + x : array of shape (n_params) + Input parameters. + + Returns + ------- + outputs : 2d-array of shape (2, n_timestep) + An array containing the simulation results. + + """ + x = np.atleast_2d(x) + paramnames = ["InflowCurve.a", + "InflowCurve.b", + "InflowCurve.tp", + "MRSignal.KappaB", + "MRSignal.KappaT", + "MRSignal.T1TissuePreContrast", + "VesselWall.DiffusionCoefficient"] # the parameters passed to the model params = {} # convert to dictionary again for i, name in enumerate(paramnames): - params[name] = str(random_params[i]) + params[name] = str(x[0,i]) # create output parameters and dump parameters - params["Problem.LesionIntensity"] = "1" # healthy: 0, sick: 1 - params["Problem.SolveFlow"] = "false" # only transport - params["VesselWall.FiltrationCoefficient"] = "9.746174233413226E-011" # Lp - params["MRSignal.T2TissuePreContrast"] = "8.000000000000000E-002" # seconds + params["Problem.LesionIntensity"] = "1" # healthy: 0, sick: 1 + params["Problem.SolveFlow"] = "false" # only transport + params["VesselWall.FiltrationCoefficient"] = "9.746174233413226E-011" + params["MRSignal.T2TissuePreContrast"] = "8.000000000000000E-002" # convert back from log scale - params["VesselWall.DiffusionCoefficient"] = str(10**(-float(params["VesselWall.DiffusionCoefficient"]))) + diff_coeff = str(10**(-float(params["VesselWall.DiffusionCoefficient"]))) + params["VesselWall.DiffusionCoefficient"] = diff_coeff params["Vessel.Grid.File"] = "./data/ratbrain.dgf" - verbose = False - data = np.array(pymsbern.run_forward_model(params, "./data/msbern.input", verbose)) + data = np.array(pymsbern.run_forward_model(params, + "./data/msbern.input", + verbose=False)) + # Prepare the outputs + t = [i*1.4 for i in range(0, 80)] + outputs = np.vstack((t, data)) - return data + return outputs # main routine if __name__ == '__main__': - # choose an initial parameter set - initial = {} - initial["MRSignal.T1TissuePreContrast"] = 1.4 # seconds - initial["InflowCurve.a"] = 3.812619924254471E+001 # scaling params mol*s/m3 - initial["InflowCurve.b"] = 1.934938693976044E+000 # baseline mol/m3 - initial["InflowCurve.tp"] = 5.000000000000000E+000 # time to peak in s - initial["VesselWall.DiffusionCoefficient"] = 14.0 # D wall - initial["MRSignal.KappaB"] = 1.502480256948131E+001 - initial["MRSignal.KappaT"] = 0.1 - - # convert to plain array for emcee - theta = np.array([v for k,v in sorted(initial.items())]) - - print(theta) - sim = run_model(theta) - print(sim) + # Define an initial parameter set + theta = np.array([ + 3.812619924254471E+001, # "InflowCurve.a" scaling params mol*s/m3 + 1.934938693976044E+000, # "InflowCurve.b" baseline mol/m3 + 5.000000000000000E+000, # "InflowCurve.tp" time to peak in s + 1.502480256948131E+001, # "MRSignal.KappaB" + 0.1, # "MRSignal.KappaT" + 1.4, # "MRSignal.T1TissuePreContrast" in seconds + 14.0, # "VesselWall.DiffusionCoefficient" D wall + ]) + sim = run_mri(theta) diff --git a/mri/uq_mri.py b/mri/uq_mri.py new file mode 100755 index 0000000000000000000000000000000000000000..58cdfa5445e238e6906a3ba1435abda4412fd864 --- /dev/null +++ b/mri/uq_mri.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +This test shows a surrogate-assisted Bayesian calibration of a time dependent + mri model. + +Author: Farid Mohammadi, M.Sc. +E-Mail: farid.mohammadi@iws.uni-stuttgart.de +Department of Hydromechanics and Modelling of Hydrosystems (LH2) +Institute for Modelling Hydraulic and Environmental Systems (IWS), University +of Stuttgart, www.iws.uni-stuttgart.de/lh2/ +Pfaffenwaldring 61 +70569 Stuttgart + +Created on Mon Feb 14 2022 + +""" + +import numpy as np +import pandas as pd +import scipy.stats as stats +import sys +import joblib +# import bayesvalidrox +sys.path.append("../src/bayesvalidrox/") +from pylink.pylink import PyLinkForwardModel +from surrogate_models.inputs import Input +from surrogate_models.surrogate_models import MetaModel +from post_processing.post_processing import PostProcessing +from bayes_inference.bayes_inference import BayesInference, Discrepancy + +import matplotlib +matplotlib.use('agg') + +if __name__ == "__main__": + + # ===================================================== + # ============= COMPUTATIONAL MODEL ================ + # ===================================================== + Model = PyLinkForwardModel() + + Model.link_type = 'Function' + Model.py_file = 'run_mri' + Model.name = 'mri' + + Model.Output.names = ['NMR signal'] # (normalized to baseline) + + # Pass data as dict for Bayesian inversion + Model.observations = {} + Model.observations['Time [s]'] = [i*1.4 for i in range(0, 80)] + Model.observations['NMR signal'] = np.loadtxt('data/msbern-sick.obs') + + # ===================================================== + # ========= PROBABILISTIC INPUT MODEL ============== + # ===================================================== + # Define the uncertain parameters with their mean and + # standard deviation + Inputs = Input() + + # "InflowCurve.a" scaling params mol*s/m3 [10, 100] + Inputs.addMarginals() + Inputs.Marginals[0].name = '$a$' + Inputs.Marginals[0].dist_type = 'uniform' + Inputs.Marginals[0].parameters = [10, 100] + + # "InflowCurve.b" baseline mol/m3 [0, 2] + Inputs.addMarginals() + Inputs.Marginals[1].name = '$b$' + Inputs.Marginals[1].dist_type = 'uniform' + Inputs.Marginals[1].parameters = [0, 2] + + # "InflowCurve.tp" time to peak in s [1, 8] + Inputs.addMarginals() + Inputs.Marginals[2].name = '$t_p$' + Inputs.Marginals[2].dist_type = 'uniform' + Inputs.Marginals[2].parameters = [1, 8] + + # "MRSignal.KappaB" [0, 100] + Inputs.addMarginals() + Inputs.Marginals[3].name = '$\\kappa_B$' + Inputs.Marginals[3].dist_type = 'uniform' + Inputs.Marginals[3].parameters = [0, 100] + + # "MRSignal.KappaT" [0, 50] + Inputs.addMarginals() + Inputs.Marginals[4].name = '$\\kappa_T$' + Inputs.Marginals[4].dist_type = 'uniform' + Inputs.Marginals[4].parameters = [0, 50] + + # "MRSignal.T1TissuePreContrast" in seconds [0.8, 3.0] + Inputs.addMarginals() + Inputs.Marginals[5].name = '$T_{1, \\textrm{pre}}$' + Inputs.Marginals[5].dist_type = 'uniform' + Inputs.Marginals[5].parameters = [0.8, 3.0] + + # "VesselWall.DiffusionCoefficient" D wall [6, 10] + Inputs.addMarginals() + Inputs.Marginals[6].name = '$Log_{10}(D_{\\omega})$' + Inputs.Marginals[6].dist_type = 'uniform' + Inputs.Marginals[6].parameters = [6, 10] + + # ===================================================== + # ========== DEFINITION OF THE METAMODEL ============ + # ===================================================== + MetaModelOpts = MetaModel(Inputs) + + # Select if you want to preserve the spatial/temporal depencencies + MetaModelOpts.dim_red_method = 'PCA' + MetaModelOpts.var_pca_threshold = 99.95 + #MetaModelOpts.n_pca_components = 12 + + # Select your metamodel method + # 1) PCE (Polynomial Chaos Expansion) 2) aPCE (arbitrary PCE) + # 3) GPE (Gaussian Process Emulator) + MetaModelOpts.meta_model_type = 'aPCE' + + # ------------------------------------------------ + # ------------- PCE Specification ---------------- + # ------------------------------------------------ + # Select the sparse least-square minimization method for + # the PCE coefficients calculation: + # 1)OLS: Ordinary Least Square 2)BRR: Bayesian Ridge Regression + # 3)LARS: Least angle regression 4)ARD: Bayesian ARD Regression + # 5)FastARD: Fast Bayesian ARD Regression + # 6)VBL: Variational Bayesian Learning + # 7)EBL: Emperical Bayesian Learning + MetaModelOpts.pce_reg_method = 'FastARD' + + # Specify the max degree to be compared by the adaptive algorithm: + # The degree with the lowest Leave-One-Out cross-validation (LOO) + # error (or the highest score=1-LOO) estimator is chosen. + # pce_deg accepts degree as a scalar or a range. + MetaModelOpts.pce_deg = 8 # np.arange(7) + + # q-quasi-norm 0<q<1 (default=1) + MetaModelOpts.pce_q_norm = 0.85 + + # Print summary of the regression results + # MetaModelOpts.verbose = True + + # ------------------------------------------------ + # ------ Experimental Design Configuration ------- + # ------------------------------------------------ + MetaModelOpts.add_ExpDesign() + + # One-shot (normal) or Sequential Adaptive (sequential) Design + MetaModelOpts.ExpDesign.Method = 'normal' + MetaModelOpts.ExpDesign.n_init_samples = 400 + + # Sampling methods + # 1) random 2) latin_hypercube 3) sobol 4) halton 5) hammersley 6) korobov + # 7) chebyshev(FT) 8) grid(FT) 9) nested_grid(FT) 10)user + MetaModelOpts.ExpDesign.sampling_method = 'user' + + # Provide the experimental design object with a hdf5 file + MetaModelOpts.ExpDesign.hdf5_file = 'ExpDesign_mri.hdf5' + + # Sequential experimental design (needed only for sequential ExpDesign) + MetaModelOpts.ExpDesign.n_new_samples = 1 + MetaModelOpts.ExpDesign.n_max_samples = 15 # 150 + MetaModelOpts.ExpDesign.mod_LOO_threshold = 1e-16 + + # Defining the measurement error, if it's known a priori + obsData = pd.DataFrame(Model.observations, columns=Model.Output.names) + DiscrepancyOpts = Discrepancy('') + DiscrepancyOpts.Type = 'Gaussian' + DiscrepancyOpts.Parameters = obsData**2 + MetaModelOpts.Discrepancy = DiscrepancyOpts + + # ------------------------------------------------ + # ------- Sequential Design configuration -------- + # ------------------------------------------------ + MetaModelOpts.adapt_verbose = True + # 1) None 2) 'equal' 3)'epsilon-decreasing' 4) 'adaptive' + MetaModelOpts.ExpDesign.tradeoff_scheme = None + # MetaModelOpts.ExpDesign.nReprications = 20 + # -------- Exploration ------ + # 1)'Voronoi' 2)'random' 3)'latin_hypercube' 4)'LOOCV' 5)'dual annealing' + MetaModelOpts.ExpDesign.explore_method = 'latin_hypercube' + + # Use when 'dual annealing' chosen + MetaModelOpts.ExpDesign.MaxFunItr = 200 + + # Use when 'Voronoi' or 'random' or 'latin_hypercube' chosen + MetaModelOpts.ExpDesign.n_canddidate = 5000 + MetaModelOpts.ExpDesign.n_cand_groups = 4 # 8 + + # -------- Exploitation ------ + # 1)'BayesOptDesign' 2)'BayesActDesign' 3)'VarOptDesign' 4)'alphabetic' + # 5)'Space-filling' + MetaModelOpts.ExpDesign.exploit_method = 'VarOptDesign' + + # BayesOptDesign -> when data is available + # 1)DKL (Kullback-Leibler Divergence) 2)DPP (D-Posterior-percision) + # 3)APP (A-Posterior-percision) # ['DKL', 'BME', 'infEntropy'] + # MetaModelOpts.ExpDesign.util_func = 'DKL' + + # VarBasedOptDesign -> when data is not available + # Only with Vornoi >>> 1)Entropy 2)EIGF, 3)LOOCV + # or a combination as a list + MetaModelOpts.ExpDesign.util_func = 'Entropy' + + # alphabetic + # 1)D-Opt (D-Optimality) 2)A-Opt (A-Optimality) + # 3)K-Opt (K-Optimality) or a combination as a list + # MetaModelOpts.ExpDesign.util_func = 'D-Opt' + + # For calculation of validation error for SeqDesign + # valid_set = np.load(f"data/Prior_{ndim}.npy") + # valid_set_outputs = np.load(f"data/origModelOutput_{ndim}.npy") + # MetaModelOpts.valid_samples = valid_set + # MetaModelOpts.valid_model_runs = {'NMR signal': valid_set_outputs} + + # >>>>>>>>>>>>>>>>>>>>>> Build Surrogate <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + # Train the meta model + PCEModel = MetaModelOpts.create_metamodel(Model) + + # Save PCE models + with open(f'PCEModel_{Model.name}.pkl', 'wb') as output: + joblib.dump(PCEModel, output, 2) + + # Load PCEModel + # with open(f'PCEModel_{Model.name}.pkl', 'rb') as input: + # PCEModel = joblib.load(input) + print(PCEModel.score_dict) + # ===================================================== + # ========= POST PROCESSING OF METAMODELS =========== + # ===================================================== + PostPCE = PostProcessing(PCEModel) + + # Compute the moments and compare with the Monte-Carlo reference + PostPCE.plotMoments() + + # Plot the evolution of the KLD,BME, and Modified LOOCV error + if MetaModelOpts.ExpDesign.Method == 'sequential': + PostPCE.seqDesignDiagnosticPlots() + + # Plot the sobol indices + sobol_cell, total_sobol = PostPCE.sobolIndicesPCE() + + # Plot to check validation visually. + PostPCE.validMetamodel(nValidSamples=3) + + # Compute and print RMSE error + PostPCE.accuracyCheckMetaModel(nSamples=10) + sys.exit() + # ===================================================== + # ======== Bayesian inference with Emulator ========== + # ===================================================== + BayesOpts = BayesInference(PCEModel) + BayesOpts.emulator = True + BayesOpts.PlotPostPred = True + + # BayesOpts.selectedIndices = [0, 3, 5, 7, 9] + # BME Bootstrap + # BayesOpts.Bootstrap = True + # BayesOpts.BootstrapItrNr = 500 + # BayesOpts.BootstrapNoise = 100 + + # Bayesian cross validation + # BayesOpts.BayesLOOCV = True + + # Select the inference method + BayesOpts.SamplingMethod = 'MCMC' + BayesOpts.MCMCnwalkers = 30 + # BayesOpts.MCMCinitSamples = np.zeros((1,10))+1e-3*np.random.randn(200, 10) + # BayesOpts.MCMCnSteps = 1000 + import emcee + BayesOpts.MCMCmoves = emcee.moves.KDEMove() + # BayesOpts.MultiProcessMCMC = True + + # ----- Define the discrepancy model ------- + BayesOpts.MeasurementError = obsData + + # # -- (Option B) -- + DiscrepancyOpts = bayesvalidrox.Discrepancy('') + DiscrepancyOpts.Type = 'Gaussian' + DiscrepancyOpts.Parameters = obsData**2 + # DiscrepancyOpts.Parameters = {'Z':np.ones(10)} + BayesOpts.Discrepancy = DiscrepancyOpts + + # -- (Option C) -- + # DiscOutputOpts = Input() + # # # OutputName = 'Z' + # DiscOutputOpts.addMarginals() + # DiscOutputOpts.Marginals[0].Name = '$\sigma^2_{\epsilon}$' + # DiscOutputOpts.Marginals[0].DistType = 'unif' + # DiscOutputOpts.Marginals[0].Parameters = [0, 10] + # BayesOpts.Discrepancy = {'known': DiscrepancyOpts, + # 'infer': Discrepancy(DiscOutputOpts)} + + # BayesOpts.BiasInputs = {'Z':np.arange(0, 10, 1.).reshape(-1,1) / 9} + # DiscOutputOpts = Input() + # # OutputName = 'lambda' + # DiscOutputOpts.addMarginals() + # DiscOutputOpts.Marginals[0].Name = '$\lambda$' + # DiscOutputOpts.Marginals[0].DistType = 'unif' + # DiscOutputOpts.Marginals[0].Parameters = [0, 1] + + # # OutputName = 'sigma_f' + # DiscOutputOpts.addMarginals() + # DiscOutputOpts.Marginals[1].Name = '$\sigma_f$' + # DiscOutputOpts.Marginals[1].DistType = 'unif' + # DiscOutputOpts.Marginals[1].Parameters = [0, 1e-4] + # BayesOpts.Discrepancy = Discrepancy(DiscOutputOpts) + # BayesOpts.Discrepancy = {'known': DiscrepancyOpts, + # 'infer': Discrepancy(DiscOutputOpts)} + # Start the calibration/inference + Bayes_PCE = BayesOpts.create_Inference() + + # Save class objects + with open(f'Bayes_{Model.name}.pkl', 'wb') as output: + joblib.dump(Bayes_PCE, output, 2)