Multi-objective BO: Fit of transient photoluminescence (TrPL) and transient microwave conductivity (trMC) with rate equations

This example demonstrates how to fit transient photoluminescence (TrPL) and transient microwave conductivity (trMC) data simultaneously using a multi-objective optimization approach. The model is based on the rate equations for charge carrier dynamics in semiconductors, which include trapping and detraping processes. The model is described by the following set of differential equations:

\[\frac{dn}{dt} = G - k_{trap} n (Bulk_{tr} - n_t) - k_{direct} n (p + p_0)\]
\[\frac{dn_t}{dt}= k_{trap} n (Bulk_{tr} - n_t) - k_{detrap} n_t (p + p_0)\]
\[\frac{dp}{dt} = G - k_{detrap} n_t (p + p_0) - k_{direct} n (p + p_0)\]
where \(n\) and \(p\) are the electron and hole charge carrier densities, \(G\) is the generation rate in m⁻³ s⁻¹, ktrap and kdetrap are the trapping and detraping rates in m³ s⁻¹, and kdirect is the bimolecular/band-to-bad recombination rate in m³ s⁻¹. The TrPL signal is given by:
\[TrPL = I_{PL} k_{direct} n (p + p_0)\]

where \(I_{PL}\) is a scaling factor for the PL signal.

The TrMC signal is given by:
\[TrMC = I_{MC} * (r_{\mu}*n + p)\]

where \(r_{\mu}\) is the mobility ratio and \(I_{MC}\) is a scaling factor for the TrMC signal.

The data fitted in this notebook is taken from the following publication: C. Kupfer et al., Journal of Material Chemistry C, 2024, 12, 95-102

[1]:
# Import necessary libraries
import warnings, os, sys
# remove warnings from the output
os.environ["PYTHONWARNINGS"] = "ignore"
warnings.filterwarnings(action='ignore', category=FutureWarning)
warnings.filterwarnings(action='ignore', category=UserWarning)
warnings.filterwarnings(action='ignore', category=RuntimeWarning)
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from copy import deepcopy

try:
    from optimpv import *
    from optimpv.pymooOpti.pymooOptimizer import PymooOptimizer
    from optimpv.RateEqfits.RateEqAgent import RateEqAgent
    from optimpv.RateEqfits.RateEqModel import *
    from optimpv.RateEqfits.Pumps import *
except Exception as e:
    sys.path.append('../') # add the path to the optimpv module
    from optimpv import *
    from optimpv.pymooOpti.pymooOptimizer import PymooOptimizer
    from optimpv.RateEqfits.RateEqAgent import RateEqAgent
    from optimpv.RateEqfits.RateEqModel import *
    from optimpv.RateEqfits.Pumps import *

Define the parameters for the simulation

[2]:
params = []

k_direct = FitParam(name = 'k_direct', value = 3.9e-17, bounds = [1e-18,1e-16], log_scale = True, rescale = True, value_type = 'float', type='range', display_name=r'$k_{\text{direct}}$', unit='m$^{3}$ s$^{-1}$', axis_type = 'log',force_log=True)
params.append(k_direct)

k_trap = FitParam(name = 'k_trap', value = 4e-18, bounds = [1e-19,1e-17], log_scale = True, rescale = True, value_type = 'float', type='range', display_name=r'$k_{\text{trap}}$', unit='m$^{3}$ s$^{-1}$', axis_type = 'log',force_log=True)
params.append(k_trap)

k_detrap = FitParam(name = 'k_detrap', value = 3.1e-18, bounds = [1e-19,1e-17], log_scale = True, rescale = True, value_type = 'float', type='range', display_name=r'$k_{\text{detrap}}$', unit='s$^{-1}$', axis_type = 'log',force_log=True)
params.append(k_detrap)

N_t_bulk = FitParam(name = 'N_t_bulk', value = 1.6e23, bounds = [1e22,5e23], log_scale = True, rescale = True, value_type = 'float', type='range', display_name=r'$N_{\text{t,bulk}}$', unit='m$^{-3}$', axis_type = 'log',force_log=True)
params.append(N_t_bulk)

I_factor_PL = FitParam(name = 'I_factor_PL', value = 1e-32, bounds = [1e-33,1e-31], log_scale = True, rescale = True, value_type = 'float', type='range', display_name=r'$I_{\text{PL}}$', unit='-', axis_type = 'log', force_log=True)
params.append(I_factor_PL)

I_factor_MC = FitParam(name = 'I_factor_MC', value = 2.2e-26, bounds = [1e-27,1e-25], log_scale = True, rescale = True, value_type = 'float', type='range', display_name=r'$I_{\text{PL}}$', unit='-', axis_type = 'log', force_log=True)
params.append(I_factor_MC)

ratio_mu = FitParam(name = 'ratio_mu', value = 4.2, bounds = [1,10], log_scale = True, rescale = True, value_type = 'float', type='range', display_name=r'$\mu_{\text{ratio}}$', unit='-', axis_type = 'linear', force_log=False)
params.append(ratio_mu)

Load and prepare the experimental data

[3]:
# Define the path to the data
curr_dir = os.getcwd()
parent_dir = os.path.abspath('../') # path to the parent directory if not in Notebooks use os.getcwd()
path2data  = os.path.join(parent_dir,'Data','perovskite_trPL_trMC')
filenames = ['S25D1_L532_F0.csv','S25D1_L532_F1.csv','S25D1_L532_F2.csv'] # list of filenames to be analyzed
res_dir = os.path.join(parent_dir,'temp') # path to the results directory

# Select Gfracs used for the data
Gfracs = [1, 0.552, 0.290, 0.136, 0.087]

[4]:
# Create a class that contains to do some basic data processing on the data
class Experiment:
    """ A set of measurements """
    def __init__(self, path2data, filenames, Gfracs, laserCenter=0, num_pts=1e3, take_log=False):
        self.path2data = path2data
        self.filenames = filenames
        self.Gfracs = Gfracs
        self.laserCenter = laserCenter
        self.num_pts = num_pts
        self.take_log = take_log

        self.get_data()
        pass


    def get_data(self):
        self.X_raw, self.y_raw_MW, self.y_raw_PL = [],[],[]
        for filename in self.filenames:
            # Create empty lists to store data
            X,y_MW, y_PL = [],[],[]

            #Load file and extract data
            with open(os.path.join(self.path2data, filename)) as f:
                for line in f:
                    tmp=line.strip("\n").split(",")
                    X.append(float(tmp[3]))
                    y_MW.append(float(tmp[4]))

                    if len(tmp)>8:
                        y_PL.append(float(tmp[10]))
                    else:
                        raise ValueError("The file does not contain PL data")

            # Create output arrays
            self.X_raw.append(np.array(X))
            self.y_raw_MW.append(np.array(y_MW))
            self.y_raw_PL.append(np.array(y_PL))
        pass


    def process_data(self, cut_rise=False, cut_time=None, cut_sigma=False):
        # Create empty lists to store data
        X_out, y_out_MW, y_out_PL = [],[],[]
        self.X_processed, self.y_processed_MW, self.y_processed_PL = [],[],[]
        self.signalParams={}
        self.background_out_PL=[]
        # Data processing:
        for X, y_MW, y_PL, Gfrac in zip(self.X_raw, self.y_raw_MW, self.y_raw_PL, self.Gfracs):

            # Subtract the background from MW and PL data
            index = np.where(X<(-10e-9))  # Calculate the background from the average of the signal up to 10ns before the peak (this buffer is to prevent the rise of the peak to affect the background)
            self.signalParams["MW_background"] = np.mean(y_MW[index])
            self.signalParams["PL_background"] = np.mean(y_PL[index])
            self.signalParams["MW_sigma"] = np.std(y_MW[index])
            self.signalParams["PL_sigma"] = np.std(y_PL[index])


            y_MW = y_MW - self.signalParams["MW_background"]
            y_PL = y_PL - self.signalParams["PL_background"]
            print('PL Sigma {}, PL background {}, MW Sigma {}, MW background {}'.format(self.signalParams["PL_sigma"],self.signalParams["PL_background"],self.signalParams["MW_sigma"],self.signalParams["MW_background"]))

            # Find the peak position
            self.signalParams["index_max_MW"] = np.argmax(abs(y_MW))
            self.signalParams["index_max_PL"] = np.argmax(abs(y_PL))

            # Find the sign of the peak
            self.signalParams["sign_max_MW"] = np.sign(y_MW[self.signalParams["index_max_MW"]])
            self.signalParams["sign_max_PL"] = np.sign(y_PL[self.signalParams["index_max_PL"]])

            # Remove datapoints at the beginning of the signal
            if cut_rise == "MW":
                index = np.where(X >= X[self.signalParams["index_max_MW"]])
            elif cut_rise == "PL":
                index = np.where(X >= X[self.signalParams["index_max_PL"]])
            elif cut_rise == "Time":
                index = np.where(X > cut_time)
            else:
                index = np.where(X > self.laserCenter)

            X = X[index]
            # Remove datapoints before the laser peak from the MW and PL signal and make sure, that the peak is positive
            y_MW = y_MW[index]*self.signalParams["sign_max_MW"]
            y_PL = y_PL[index]*self.signalParams["sign_max_PL"]

            # Remove datapoints that aren't significant enough (in either measurement)
            if cut_sigma:
                sigma = float(cut_sigma)
                index = np.where((np.abs(y_MW)>sigma*self.signalParams["MW_sigma"]) & (np.abs(y_PL)>sigma*self.signalParams["PL_sigma"]))

                X = X[index]
                y_MW = y_MW[index]
                y_PL = y_PL[index]


            # Interpolate to get num_pts
            X_interp = np.geomspace(X[1],X[-1],int(self.num_pts))

            # Add 0 to the beginning of X_interp
            X_interp = np.insert(X_interp,0,0)
            y_interp_MW = np.interp(X_interp,X,y_MW)
            y_interp_PL = np.interp(X_interp,X,y_PL)

            # Take the log of the data
            if self.take_log:
                y_interp_MW = np.log10(y_interp_MW)
                y_interp_PL = np.log10(y_interp_PL)

                # Remove all data points where either signal is NaN
                mask_NaNs = np.logical_or(np.isnan(y_interp_PL), np.isnan(y_interp_MW))
                X_interp = X_interp[~mask_NaNs]
                y_interp_MW = y_interp_MW[~mask_NaNs]
                y_interp_PL = y_interp_PL[~mask_NaNs]
                print('Removed {} Data Points while taking the logarithm!'.format(np.count_nonzero(mask_NaNs)))

            # Append the data to the output
            for i in range(len(X_interp)):
                X_out.append([X_interp[i],Gfrac])
                y_out_MW.append(y_interp_MW[i])
                y_out_PL.append(y_interp_PL[i])
                self.background_out_PL.append(self.signalParams["PL_sigma"]*np.sqrt(2/np.pi))

            self.X_processed.append(np.array(X_interp))
            self.y_processed_MW.append(np.array(y_interp_MW))
            self.y_processed_PL.append(np.array(y_interp_PL))

        # Convert the output to arrays
        self.X = np.array(X_out)
        self.y_MW = np.array(y_out_MW)
        self.y_PL = np.array(y_out_PL)
        pass

[5]:
# Load the data and process it
data_exp = Experiment(path2data, filenames, Gfracs, laserCenter=2.8E-8, take_log=False)
data_exp.process_data(cut_rise=False, cut_time=None ,cut_sigma=0)
X = data_exp.X
y_MW = data_exp.y_MW
y_PL = data_exp.y_PL
back_PL = data_exp.background_out_PL

# remove all point where PL is below 0
mask = np.where(y_PL<0)
y_PL = np.delete(y_PL,mask)
X = np.delete(X,mask,axis=0)
y_MW = np.delete(y_MW,mask)
back_PL = np.delete(back_PL,mask)
# remove all point where MW is below 0
mask = np.where(y_MW<0)
y_MW = np.delete(y_MW,mask)
X = np.delete(X,mask,axis=0)
y_PL = np.delete(y_PL,mask)
back_PL = np.delete(back_PL,mask)
from sklearn.preprocessing import minmax_scale
# Assign weights based on the signal strength
weight_PL = None #1/(np.abs(y_PL))
weight_MW = None #1/(np.abs(y_MW))
# weight_MW = minmax_scale(weight_MW, feature_range=(1,1000))

PL Sigma 2.9602988876119335e-05, PL background -0.003598912016122306, MW Sigma 3.780285415804477e-05, MW background -0.0007008701257225716
PL Sigma 3.0399603508164594e-05, PL background -0.003610847480546366, MW Sigma 4.040993545427417e-05, MW background -0.0008486578742130771
PL Sigma 2.5489343983201138e-05, PL background -0.0031102876606446113, MW Sigma 3.75774071903544e-05, MW background -0.0009373141997225259
[6]:
# RateEqModel parameters
fpu = 1e3 # Frequency of the pump laser in Hz
N0 = 1.041e24 # Initial carrier density in m-3
background = 0 # Background illumination
Gfracs = [1, 0.552, 0.290] # Gfracs used for the data

# Define the Agent and the target metric/loss function
metric = 'mse'
loss = 'soft_l1'
pump_args = {'N0': N0, 'fpu': fpu , 'background' : background, }

# 50 log spaced points data X, y_PL, y_MW
# t_min except 0
num_pts = 20
t_min = X[X[:,0]>0,0].min()
X_log = np.geomspace(t_min,X[:,0].max(),num_pts)
X_log = np.insert(X_log,0,0)
# get teh closest 50 points to the log spaced points
X_50 = np.zeros((int(len(X_log)*len(Gfracs)),2))
y_PL_50 = np.zeros(int(len(X_log)*len(Gfracs)))
y_MW_50 = np.zeros(int(len(X_log)*len(Gfracs)))

idx_main = 0
for g in Gfracs:
    idx = 0
    for i in range(len(X_log)):
        index = np.argmin(abs(X[X[:,1]==g,0]-X_log[idx]))
        X_50[idx_main] = X[X[:,1]==g][index]
        y_PL_50[idx_main] = y_PL[X[:,1]==g][index]
        y_MW_50[idx_main] = y_MW[X[:,1]==g][index]
        idx += 1
        idx_main += 1

RateEq = RateEqAgent(params, [X,X], [y_PL,y_MW], model = BTD_model, pump_model = initial_carrier_density, pump_args = pump_args, fixed_model_args = {}, metric = [metric,metric], loss = [loss,loss], threshold=[0.5,0.5],minimize=[True,True],exp_format=['trPL','trMC'],detection_limit=1e-5, weight=[weight_PL,weight_MW], transforms ='log')

# Not necessary, but here we run the simulation with the parameters to test the model
# Run the simulation with the parameters
y_PL_fit = RateEq.run(parameters={},exp_format='trPL')
y_MC_fit = RateEq.run(parameters={},exp_format='trMC')
print(RateEq.run_Ax(parameters={}))

# Make a plot with 2 subplots for PL and MW
fig, (ax1, ax2) = plt.subplots(1, 2,figsize=(10,5))

for i in range(len(data_exp.X_processed)):
    ax1.semilogx(data_exp.X_processed[i], data_exp.y_processed_PL[i], 'o', label=data_exp.Gfracs[i],color='C'+str(i),alpha=0.025)
    ax1.plot(X[X[:,1]==Gfracs[i],0], y_PL_fit[X[:,1]==Gfracs[i]],'-',label=str(Gfracs[i]), color = 'C'+str(i), linewidth=2)
    ax2.semilogx(data_exp.X_processed[i], data_exp.y_processed_MW[i], 'o', label=data_exp.Gfracs[i],color='C'+str(i),alpha=0.025)
    ax2.plot(X[X[:,1]==Gfracs[i],0], y_MC_fit[X[:,1]==Gfracs[i]],'-',label=str(Gfracs[i]), color = 'C'+str(i), linewidth=2)
    # ax1.plot(X_50[X_50[:,1]==Gfracs[i],0], y_PL_50[X_50[:,1]==Gfracs[i]],'o',label=str(Gfracs[i]), color = 'r')
    # ax2.plot(X_50[X_50[:,1]==Gfracs[i],0], y_MW_50[X_50[:,1]==Gfracs[i]],'o',label=str(Gfracs[i]), color = 'r')
ax1.set_xscale('log')
ax1.set_yscale('log')
ax2.set_xscale('log')
ax2.set_yscale('log')
ax1.set_xlabel('Time (s)')
ax1.set_ylabel('PL (a.u.)')
ax2.set_xlabel('Time (s)')
ax2.set_ylabel('MW (a.u.)')
{'RateEq_trPL_mse_soft_l1': 0.06916844589940263, 'RateEq_trMC_mse_soft_l1': 0.0019317690776468055}
[6]:
Text(0, 0.5, 'MW (a.u.)')
../_images/examples_MO_trPL_trMC_pymoo_8_2.png

Run the optimization

[7]:
# Define the optimizer
optimizer = PymooOptimizer(params=params, agents=RateEq, algorithm='NSGA2', pop_size=20, n_gen=100, name='pymoo_multi_obj', verbose_logging=True,max_parallelism=20, )
[8]:
res = optimizer.optimize() # run the optimization with ax
[INFO 12-05 12:59:05] optimpv.pymooOptimizer: Starting optimization using NSGA2 algorithm
[INFO 12-05 12:59:05] optimpv.pymooOptimizer: Population size: 20, Generations: 100
[INFO 12-05 12:59:05] optimpv.pymooOptimizer: Generation 1: Best objective = 0.017988
==========================================================
n_gen  |  n_eval  | n_nds  |      eps      |   indicator
==========================================================
     1 |       20 |      3 |             - |             -
[INFO 12-05 12:59:06] optimpv.pymooOptimizer: Generation 2: Best objective = 0.017988
     2 |       40 |      4 |  0.0805748586 |             f
[INFO 12-05 12:59:06] optimpv.pymooOptimizer: Generation 3: Best objective = 0.016176
     3 |       60 |      5 |  0.1858419378 |         ideal
[INFO 12-05 12:59:07] optimpv.pymooOptimizer: Generation 4: Best objective = 0.015128
     4 |       80 |      7 |  0.0252655532 |         ideal
[INFO 12-05 12:59:07] optimpv.pymooOptimizer: Generation 5: Best objective = 0.015128
     5 |      100 |      9 |  0.0665949978 |         ideal
[INFO 12-05 12:59:08] optimpv.pymooOptimizer: Generation 6: Best objective = 0.011929
     6 |      120 |      9 |  0.0145959918 |         ideal
[INFO 12-05 12:59:08] optimpv.pymooOptimizer: Generation 7: Best objective = 0.010065
     7 |      140 |     11 |  0.0084330871 |         ideal
[INFO 12-05 12:59:09] optimpv.pymooOptimizer: Generation 8: Best objective = 0.009996
     8 |      160 |     11 |  2.1240533128 |         nadir
[INFO 12-05 12:59:09] optimpv.pymooOptimizer: Generation 9: Best objective = 0.009518
     9 |      180 |     12 |  0.0067111909 |         ideal
[INFO 12-05 12:59:10] optimpv.pymooOptimizer: Generation 10: Best objective = 0.009518
    10 |      200 |     11 |  0.5448382777 |         nadir
[INFO 12-05 12:59:10] optimpv.pymooOptimizer: Generation 11: Best objective = 0.009518
    11 |      220 |     10 |  0.0133563379 |         ideal
[INFO 12-05 12:59:11] optimpv.pymooOptimizer: Generation 12: Best objective = 0.009518
    12 |      240 |      7 |  0.2533072116 |         nadir
[INFO 12-05 12:59:13] optimpv.pymooOptimizer: Generation 13: Best objective = 0.009011
    13 |      260 |      6 |  0.0228792090 |         ideal
[INFO 12-05 12:59:13] optimpv.pymooOptimizer: Generation 14: Best objective = 0.009011
    14 |      280 |      8 |  0.0056443284 |         ideal
[INFO 12-05 12:59:14] optimpv.pymooOptimizer: Generation 15: Best objective = 0.008801
    15 |      300 |     10 |  0.0147442487 |         ideal
[INFO 12-05 12:59:14] optimpv.pymooOptimizer: Generation 16: Best objective = 0.008798
    16 |      320 |      7 |  4.1308315741 |         nadir
[INFO 12-05 12:59:16] optimpv.pymooOptimizer: Generation 17: Best objective = 0.008798
    17 |      340 |      7 |  0.0108028813 |         ideal
[INFO 12-05 12:59:17] optimpv.pymooOptimizer: Generation 18: Best objective = 0.008774
    18 |      360 |      5 |  5.0611785469 |         nadir
[INFO 12-05 12:59:17] optimpv.pymooOptimizer: Generation 19: Best objective = 0.008774
    19 |      380 |      5 |  0.0332182804 |             f
[INFO 12-05 12:59:19] optimpv.pymooOptimizer: Generation 20: Best objective = 0.008722
    20 |      400 |      4 |  0.0048603838 |         ideal
[INFO 12-05 12:59:21] optimpv.pymooOptimizer: Generation 21: Best objective = 0.008722
    21 |      420 |      5 |  0.0232953834 |         ideal
[INFO 12-05 12:59:22] optimpv.pymooOptimizer: Generation 22: Best objective = 0.008722
    22 |      440 |      8 |  0.0323922443 |             f
[INFO 12-05 12:59:23] optimpv.pymooOptimizer: Generation 23: Best objective = 0.008722
    23 |      460 |      6 |  0.1454173514 |         nadir
[INFO 12-05 12:59:24] optimpv.pymooOptimizer: Generation 24: Best objective = 0.008722
    24 |      480 |      9 |  0.0313622160 |             f
[INFO 12-05 12:59:25] optimpv.pymooOptimizer: Generation 25: Best objective = 0.008720
    25 |      500 |     13 |  0.0068122544 |         nadir
[INFO 12-05 12:59:25] optimpv.pymooOptimizer: Generation 26: Best objective = 0.008718
    26 |      520 |     14 |  0.0094633096 |         ideal
[INFO 12-05 12:59:26] optimpv.pymooOptimizer: Generation 27: Best objective = 0.008718
    27 |      540 |     13 |  0.0139464642 |             f
[INFO 12-05 12:59:26] optimpv.pymooOptimizer: Generation 28: Best objective = 0.008718
    28 |      560 |     15 |  0.0028448793 |         ideal
[INFO 12-05 12:59:27] optimpv.pymooOptimizer: Generation 29: Best objective = 0.008718
    29 |      580 |     19 |  0.4906859698 |         nadir
[INFO 12-05 12:59:28] optimpv.pymooOptimizer: Generation 30: Best objective = 0.008718
    30 |      600 |     13 |  0.0166477669 |         ideal
[INFO 12-05 12:59:28] optimpv.pymooOptimizer: Generation 31: Best objective = 0.008713
    31 |      620 |     13 |  0.0690105080 |         nadir
[INFO 12-05 12:59:29] optimpv.pymooOptimizer: Generation 32: Best objective = 0.008713
    32 |      640 |     16 |  0.0036625650 |             f
[INFO 12-05 12:59:30] optimpv.pymooOptimizer: Generation 33: Best objective = 0.008713
    33 |      660 |     14 |  0.1462261896 |         nadir
[INFO 12-05 12:59:30] optimpv.pymooOptimizer: Generation 34: Best objective = 0.008713
    34 |      680 |     13 |  0.0091392532 |             f
[INFO 12-05 12:59:32] optimpv.pymooOptimizer: Generation 35: Best objective = 0.008713
    35 |      700 |     15 |  0.0000515228 |             f
[INFO 12-05 12:59:34] optimpv.pymooOptimizer: Generation 36: Best objective = 0.008713
    36 |      720 |     18 |  0.0086364360 |         ideal
[INFO 12-05 12:59:34] optimpv.pymooOptimizer: Generation 37: Best objective = 0.008593
    37 |      740 |     13 |  0.0151325289 |         ideal
[INFO 12-05 12:59:36] optimpv.pymooOptimizer: Generation 38: Best objective = 0.008572
    38 |      760 |     14 |  0.0027364208 |         ideal
[INFO 12-05 12:59:38] optimpv.pymooOptimizer: Generation 39: Best objective = 0.008572
    39 |      780 |      9 |  0.0057793014 |         ideal
[INFO 12-05 12:59:38] optimpv.pymooOptimizer: Generation 40: Best objective = 0.008572
    40 |      800 |     10 |  0.0094331661 |             f
[INFO 12-05 12:59:39] optimpv.pymooOptimizer: Generation 41: Best objective = 0.008572
    41 |      820 |     14 |  0.0050950759 |             f
[INFO 12-05 12:59:40] optimpv.pymooOptimizer: Generation 42: Best objective = 0.008572
    42 |      840 |     14 |  0.0147708983 |         ideal
[INFO 12-05 12:59:40] optimpv.pymooOptimizer: Generation 43: Best objective = 0.008328
    43 |      860 |     17 |  0.0292077359 |         ideal
[INFO 12-05 12:59:41] optimpv.pymooOptimizer: Generation 44: Best objective = 0.008328
    44 |      880 |     12 |  0.0212495327 |             f
[INFO 12-05 12:59:41] optimpv.pymooOptimizer: Generation 45: Best objective = 0.008328
    45 |      900 |     12 |  0.0126330112 |         nadir
[INFO 12-05 12:59:42] optimpv.pymooOptimizer: Generation 46: Best objective = 0.008232
    46 |      920 |     13 |  0.0112655764 |         ideal
[INFO 12-05 12:59:44] optimpv.pymooOptimizer: Generation 47: Best objective = 0.008079
    47 |      940 |     12 |  0.0175320081 |         ideal
[INFO 12-05 12:59:44] optimpv.pymooOptimizer: Generation 48: Best objective = 0.008079
    48 |      960 |     14 |  0.0087715667 |             f
[INFO 12-05 12:59:45] optimpv.pymooOptimizer: Generation 49: Best objective = 0.008079
    49 |      980 |     14 |  0.0009351437 |             f
[INFO 12-05 12:59:45] optimpv.pymooOptimizer: Generation 50: Best objective = 0.008078
    50 |     1000 |     16 |  0.1427158064 |         nadir
[INFO 12-05 12:59:46] optimpv.pymooOptimizer: Generation 51: Best objective = 0.008078
    51 |     1020 |     17 |  0.0003877015 |             f
[INFO 12-05 12:59:47] optimpv.pymooOptimizer: Generation 52: Best objective = 0.008078
    52 |     1040 |     10 |  0.1364997048 |         nadir
[INFO 12-05 12:59:47] optimpv.pymooOptimizer: Generation 53: Best objective = 0.008078
    53 |     1060 |     14 |  0.0201600723 |             f
[INFO 12-05 12:59:48] optimpv.pymooOptimizer: Generation 54: Best objective = 0.008078
    54 |     1080 |     14 |  0.0063372363 |             f
[INFO 12-05 12:59:48] optimpv.pymooOptimizer: Generation 55: Best objective = 0.008078
    55 |     1100 |     12 |  3.5608504748 |         nadir
[INFO 12-05 12:59:49] optimpv.pymooOptimizer: Generation 56: Best objective = 0.008078
    56 |     1120 |     11 |  0.0065829251 |             f
[INFO 12-05 12:59:49] optimpv.pymooOptimizer: Generation 57: Best objective = 0.008063
    57 |     1140 |      9 |  0.1015375491 |         ideal
[INFO 12-05 12:59:50] optimpv.pymooOptimizer: Generation 58: Best objective = 0.008045
    58 |     1160 |      9 |  0.0221310869 |         ideal
[INFO 12-05 12:59:50] optimpv.pymooOptimizer: Generation 59: Best objective = 0.008045
    59 |     1180 |     10 |  0.0241828979 |         ideal
[INFO 12-05 12:59:51] optimpv.pymooOptimizer: Generation 60: Best objective = 0.007995
    60 |     1200 |     12 |  0.0046671393 |         ideal
[INFO 12-05 12:59:51] optimpv.pymooOptimizer: Generation 61: Best objective = 0.006973
    61 |     1220 |     11 |  0.0870943561 |         ideal
[INFO 12-05 12:59:52] optimpv.pymooOptimizer: Generation 62: Best objective = 0.006921
    62 |     1240 |     15 |  0.0043956875 |         ideal
[INFO 12-05 12:59:52] optimpv.pymooOptimizer: Generation 63: Best objective = 0.006921
    63 |     1260 |     18 |  0.0163376095 |             f
[INFO 12-05 12:59:53] optimpv.pymooOptimizer: Generation 64: Best objective = 0.006921
    64 |     1280 |     20 |  0.0108176708 |             f
[INFO 12-05 12:59:53] optimpv.pymooOptimizer: Generation 65: Best objective = 0.006850
    65 |     1300 |     20 |  0.0060111684 |         ideal
[INFO 12-05 12:59:54] optimpv.pymooOptimizer: Generation 66: Best objective = 0.006258
    66 |     1320 |     20 |  0.0469148942 |         ideal
[INFO 12-05 12:59:54] optimpv.pymooOptimizer: Generation 67: Best objective = 0.006258
    67 |     1340 |     20 |  0.0066412641 |             f
[INFO 12-05 12:59:55] optimpv.pymooOptimizer: Generation 68: Best objective = 0.005864
    68 |     1360 |     20 |  0.0474700839 |         ideal
[INFO 12-05 12:59:55] optimpv.pymooOptimizer: Generation 69: Best objective = 0.005864
    69 |     1380 |     20 |  0.0109240624 |             f
[INFO 12-05 12:59:56] optimpv.pymooOptimizer: Generation 70: Best objective = 0.005864
    70 |     1400 |     20 |  0.0070489221 |             f
[INFO 12-05 12:59:56] optimpv.pymooOptimizer: Generation 71: Best objective = 0.005864
    71 |     1420 |     20 |  0.0114690899 |             f
[INFO 12-05 12:59:57] optimpv.pymooOptimizer: Generation 72: Best objective = 0.005864
    72 |     1440 |     20 |  0.0032436096 |         ideal
[INFO 12-05 12:59:58] optimpv.pymooOptimizer: Generation 73: Best objective = 0.005813
    73 |     1460 |     20 |  0.0042554434 |         ideal
[INFO 12-05 12:59:59] optimpv.pymooOptimizer: Generation 74: Best objective = 0.005232
    74 |     1480 |     20 |  0.0684402045 |         ideal
[INFO 12-05 12:59:59] optimpv.pymooOptimizer: Generation 75: Best objective = 0.005232
    75 |     1500 |     20 |  0.0070399354 |             f
[INFO 12-05 13:00:00] optimpv.pymooOptimizer: Generation 76: Best objective = 0.005099
    76 |     1520 |     20 |  0.0154819389 |         ideal
[INFO 12-05 13:00:00] optimpv.pymooOptimizer: Generation 77: Best objective = 0.005099
    77 |     1540 |     20 |  0.0098073254 |             f
[INFO 12-05 13:00:01] optimpv.pymooOptimizer: Generation 78: Best objective = 0.005099
    78 |     1560 |     20 |  0.3616104143 |         nadir
[INFO 12-05 13:00:01] optimpv.pymooOptimizer: Generation 79: Best objective = 0.005099
    79 |     1580 |     20 |  0.0161486366 |             f
[INFO 12-05 13:00:02] optimpv.pymooOptimizer: Generation 80: Best objective = 0.005099
    80 |     1600 |     20 |  0.0119090536 |             f
[INFO 12-05 13:00:02] optimpv.pymooOptimizer: Generation 81: Best objective = 0.004526
    81 |     1620 |     20 |  0.0691755826 |         ideal
[INFO 12-05 13:00:03] optimpv.pymooOptimizer: Generation 82: Best objective = 0.003851
    82 |     1640 |     20 |  0.0754620801 |         ideal
[INFO 12-05 13:00:03] optimpv.pymooOptimizer: Generation 83: Best objective = 0.003851
    83 |     1660 |     20 |  0.0159765280 |             f
[INFO 12-05 13:00:04] optimpv.pymooOptimizer: Generation 84: Best objective = 0.003756
    84 |     1680 |     20 |  0.0120580046 |         ideal
[INFO 12-05 13:00:04] optimpv.pymooOptimizer: Generation 85: Best objective = 0.003756
    85 |     1700 |     20 |  0.0054148448 |             f
[INFO 12-05 13:00:05] optimpv.pymooOptimizer: Generation 86: Best objective = 0.003736
    86 |     1720 |     20 |  0.0218163941 |         ideal
[INFO 12-05 13:00:05] optimpv.pymooOptimizer: Generation 87: Best objective = 0.003736
    87 |     1740 |     20 |  0.0028697210 |         ideal
[INFO 12-05 13:00:05] optimpv.pymooOptimizer: Generation 88: Best objective = 0.003736
    88 |     1760 |     20 |  0.0102109483 |             f
[INFO 12-05 13:00:06] optimpv.pymooOptimizer: Generation 89: Best objective = 0.003736
    89 |     1780 |     20 |  0.0104045444 |             f
[INFO 12-05 13:00:06] optimpv.pymooOptimizer: Generation 90: Best objective = 0.003736
    90 |     1800 |     20 |  0.0035027299 |         ideal
[INFO 12-05 13:00:07] optimpv.pymooOptimizer: Generation 91: Best objective = 0.003736
    91 |     1820 |     20 |  0.0141494023 |         ideal
[INFO 12-05 13:00:07] optimpv.pymooOptimizer: Generation 92: Best objective = 0.003736
    92 |     1840 |     20 |  0.0037389118 |             f
[INFO 12-05 13:00:08] optimpv.pymooOptimizer: Generation 93: Best objective = 0.003728
    93 |     1860 |     20 |  0.0098648343 |             f
[INFO 12-05 13:00:08] optimpv.pymooOptimizer: Generation 94: Best objective = 0.003728
    94 |     1880 |     20 |  0.2902102440 |         nadir
[INFO 12-05 13:00:09] optimpv.pymooOptimizer: Generation 95: Best objective = 0.003678
    95 |     1900 |     20 |  0.0029118373 |         ideal
[INFO 12-05 13:00:10] optimpv.pymooOptimizer: Generation 96: Best objective = 0.003672
    96 |     1920 |     20 |  0.0116696042 |         nadir
[INFO 12-05 13:00:11] optimpv.pymooOptimizer: Generation 97: Best objective = 0.003357
    97 |     1940 |     20 |  0.0176820019 |         ideal
[INFO 12-05 13:00:11] optimpv.pymooOptimizer: Generation 98: Best objective = 0.003357
    98 |     1960 |     20 |  0.0139542996 |         ideal
[INFO 12-05 13:00:12] optimpv.pymooOptimizer: Generation 99: Best objective = 0.003357
    99 |     1980 |     20 |  0.0963768010 |         nadir
[INFO 12-05 13:00:12] optimpv.pymooOptimizer: Generation 100: Best objective = 0.003357
[INFO 12-05 13:00:12] optimpv.pymooOptimizer: Optimization completed after 101 generations
[INFO 12-05 13:00:12] optimpv.pymooOptimizer: Number of function evaluations: 2000
[INFO 12-05 13:00:12] optimpv.pymooOptimizer: Final population size: 20
[INFO 12-05 13:00:12] optimpv.pymooOptimizer: Pareto front size: 20
   100 |     2000 |     20 |  0.0041822306 |             f
[9]:
# Plot the density of the exploration of the parameters
# this gives a nice visualization of where the optimizer focused its exploration and may show some correlation between the parameters
plot_dens = True
if plot_dens:
    from optimpv.posterior.exploration_density import *
    best_parameters = {}
    for p in optimizer.params:
        best_parameters[p.name] = p.value

    fig_dens, ax_dens = plot_density_exploration(params, optimizer = optimizer, best_parameters = best_parameters, optimizer_type = 'pymoo')
../_images/examples_MO_trPL_trMC_pymoo_12_0.png
[10]:
from pymoo.visualization.scatter import Scatter

Scatter().add(res.F, label="Pareto Front").show()

[10]:
<pymoo.visualization.scatter.Scatter at 0x7d109b2a5400>
../_images/examples_MO_trPL_trMC_pymoo_13_1.png
[11]:
# get the best parameters and update the params list in the optimizer and the agent
optimizer.update_params_with_best_balance() # update the params list in the optimizer with the best parameters
RateEq.params = optimizer.params # update the params list in the agent with the best parameters

# print the best parameters
print('Best parameters:')
for p in optimizer.params:
    print(p.name, 'fitted value:', p.value)
Best parameters:
k_direct fitted value: 4.8851551775305625e-17
k_trap fitted value: 1.2457206084194873e-18
k_detrap fitted value: 1.12057774276303e-18
N_t_bulk fitted value: 1.4353740295502752e+23
I_factor_PL fitted value: 1.0426213943261586e-32
I_factor_MC fitted value: 2.364070926013898e-26
ratio_mu fitted value: 4.730836125815627
[12]:
import matplotlib
# import itertools
from itertools import combinations
comb = list(combinations(optimizer.all_metrics, 2))
threshold_list = []
for i in range(len(optimizer.agents)):
    for j in range(len(optimizer.agents[i].threshold)):
        threshold_list.append(optimizer.agents[i].threshold[j])
threshold_comb = list(combinations(threshold_list, 2))
pareto = np.asarray(res.F)

cm = matplotlib.colormaps.get_cmap('viridis')
df = optimizer.get_df_from_pymoo() # get the dataframe from the optimizer
# create pareto df
dum_dic = {}

for i , metr in enumerate(optimizer.all_metrics):
    if i not in df.keys():
        dum_dic[metr] = pareto[:, i]

df_pareto = pd.DataFrame(dum_dic)
dum_dic = RateEq.run_Ax(parameters={})
best_balanced = [ dum_dic[metr] for metr in optimizer.all_metrics]

for c,t_c in zip(comb,threshold_comb):
    plt.figure(figsize=(10, 10))
    plt.scatter(df[c[0]],df[c[1]],c=df.index, cmap=cm, marker='o', s=100) # plot the points with color according to the iteration
    cbar = plt.colorbar()
    cbar.set_label('Iteration')
    sorted_df = df_pareto.sort_values(by=c[0])
    plt.plot(sorted_df[c[0]],sorted_df[c[1]],'r')
    plt.scatter(t_c[0],t_c[1],c='r', marker='x', s=100) # plot the threshold
    plt.scatter(best_balanced[0],best_balanced[1],c='k', marker='*', s=200, label='Best balanced') # plot the best balanced point
    plt.xlabel(c[0])
    plt.ylabel(c[1])
    plt.xscale('log')
    plt.yscale('log')


    plt.show()
../_images/examples_MO_trPL_trMC_pymoo_15_0.png
[13]:
# Run the simulation with the parameters
y_PL_fit = RateEq.run(parameters={},exp_format='trPL')
y_MC_fit = RateEq.run(parameters={},exp_format='trMC')
print(RateEq.run_Ax(parameters={}))
# Make a plot with 2 subplots for PL and MW
fig, (ax1, ax2) = plt.subplots(1, 2,figsize=(10,5))

for i in range(len(Gfracs)):
    ax1.semilogx(X[X[:,1]==Gfracs[i],0], y_PL[X[:,1]==Gfracs[i]], 'o', color='C'+str(i),alpha=0.025)
    ax1.plot(X[X[:,1]==Gfracs[i],0], y_PL_fit[X[:,1]==Gfracs[i]],'-', color = 'C'+str(i), linewidth=2)
    ax2.semilogx(X[X[:,1]==Gfracs[i],0], y_MW[X[:,1]==Gfracs[i]], 'o', color='C'+str(i),alpha=0.025)
    ax2.plot(X[X[:,1]==Gfracs[i],0], y_MC_fit[X[:,1]==Gfracs[i]],'-', color = 'C'+str(i), linewidth=2)
ax1.set_xscale('log')
ax1.set_yscale('log')
ax2.set_xscale('log')
ax2.set_yscale('log')
ax1.set_xlabel('Time (s)')
ax1.set_ylabel('PL (a.u.)')
ax2.set_xlabel('Time (s)')
ax2.set_ylabel('MW (a.u.)')
plt.show()

{'RateEq_trPL_mse_soft_l1': 0.06161824709141728, 'RateEq_trMC_mse_soft_l1': 0.008058833979098345}
../_images/examples_MO_trPL_trMC_pymoo_16_1.png