fpex0.fpex0
1import sys 2import traceback 3 4import numpy as np 5from scipy import optimize 6import time 7 8 9def simulate(FPEX0setup, pvec, odeoptions={}, method="BDF"): 10 """ 11 Simulates Fokker-Planck with specified parameters for FP drift, diffusion, and initial function. 12 13 ## Takes 14 **FPEX0setup** 15 <br> An FPEX0 setup configuration (`fpex0.setup.Setup`). 16 17 **p_all** 18 <br> Vector of parameters, coupled to FPEX0setup. 19 <br> It must have the same scheme as FPEX0setup.Parameters.p_0, 20 then simulate() knows how to extract the parameters correctly. 21 Usually this is ensured by the optimizer, who got the initial parameters and changes them through 22 optimization steps. 23 24 **odeoptions** 25 <br> kwargs passed to the scipy solve_ivp ODE solver 26 (https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html). 27 28 **method** 29 <br> The method scipy solve_ivp should use. 30 <br> Default is BDF, an an implicit multistep method. 31 32 33 ## Returns 34 **solution** 35 <br> A scipy solve_ivp bunch object. Important parameters are 36 - t: time points 37 - y: corresponding values 38 - sol: A (here) callable solution 39 40 See link above for details. 41 42 ### Comments 43 The function will not let you set dense_output = False in odeoptions. 44 """ 45 # extract parameters 46 p_FPdrift = FPEX0setup.Parameters.extract_p_FPdrift(pvec) 47 p_FPdiffusion = FPEX0setup.Parameters.extract_p_FPdiffusion(pvec) 48 p_IC = FPEX0setup.Parameters.extract_p_iniDist(pvec) 49 50 # evaluate initial distribution 51 gridT = FPEX0setup.Grid.gridT 52 u0 = FPEX0setup.IniDistFcn(gridT, p_IC) 53 54 # retrieve "time" horizon for integrator 55 t0tf = FPEX0setup.Grid.gridTdot[[0, -1]] 56 57 # generate right hand side, jacobian 58 FPrhs = FPEX0setup.make_rhsFcn(p_FPdrift, p_FPdiffusion) 59 FPjac = FPEX0setup.make_jacFcn(p_FPdrift, p_FPdiffusion) 60 61 # setup integrator and update options, jacobian therein 62 integrator = FPEX0setup.Integration.integrator 63 odeoptions = FPEX0setup.Integration.updateOptions(odeoptions) 64 odeoptions["dense_output"] = True # dense_output for evaluable solution 65 odeoptions = FPEX0setup.Integration.updateJacobian(FPjac) 66 67 68 # start integration 69 try: 70 sTime = time.time() 71 solution = integrator(FPrhs, t0tf, u0, method,**odeoptions) 72 duration = time.time() - sTime 73 print(f"Simulate: {duration:.3f}s") 74 except: 75 exception = sys.exc_info() 76 traceback.print_exception(*exception) 77 print('Integration failed!') 78 79 duration = None 80 solution = None 81 82 return solution 83 84 85def fit(FPEX0setup, optimizer='lsq'): 86 """ 87 Fits the Fokker-Planck simulation to the given measurements as an optimization of the parameters 88 for drift, diffusion and the initial distribution. 89 90 ## Takes 91 **FPEX0setup** 92 <br> An FPEX0 setup configuration (`fpex0.setup.Setup`). 93 94 **optimizer** 95 <br> The optimizer that should be used. So far only least squares is implemented. 96 97 98 ## Returns 99 **result** 100 <br> A scipy.optimize.OptimizeResult object, with fit.x holding the parameter vector found. 101 102 """ 103 # retrieve parameter values, bounds, indices 104 p_0 = FPEX0setup.Parameters.p0 105 p_lb = FPEX0setup.Parameters.p_lb 106 p_ub = FPEX0setup.Parameters.p_ub 107 108 # set function that computes the residual vector 109 resvecfun = lambda p: residual(FPEX0setup, p) 110 111 # optimization 112 print(f'Running {optimizer}.\n') 113 114 if optimizer.lower() == 'lsq': 115 lsq_opts = {} 116 # set options 117 lsq_opts["jac"] = '3-point' 118 lsq_opts["max_nfev"] = 100000 # max function evaluations 119 # tolerances 120 lsq_opts["xtol"] = 1e-6 # x-step 121 lsq_opts["ftol"] = 1e-10 # function-step 122 lsq_opts["gtol"] = 1.0 # norm of gradient, quite high, but okay for FD 123 124 lsq_opts["x_scale"] = 'jac' # let set scipy set scale with jacobian 125 lsq_opts["verbose"] = 2 # give detailed progress information 126 result = optimize.least_squares(resvecfun, p_0, bounds=(p_lb, p_ub), **lsq_opts) 127 128 return result 129 130 else: 131 raise ValueError("Your specified optimizer is not yet implemented. \nYou are welcomed to contact us by mail if interested in contributing!") 132 133 134def residual(FPEX0setup, p_all): 135 """ 136 Calculates the residual vector of measurements and simulation values, i.e. measVals - simVals 137 at suitable points. 138 139 140 ## Takes 141 **FPEX0setup** 142 <br> An FPEX0 setup configuration (`fpex0.setup.Setup`). 143 144 **p_all** 145 <br> Vector of parameters, coupled to FPEX0setup. 146 <br> It must have the same scheme as FPEX0setup.Parameters.p_0, 147 then residual() knows how to use the parameters correctly. 148 Usually this is ensured by the optimizer, who got the initial parameters and changes them through 149 optimization steps. 150 151 ## Returns 152 **resvec** 153 <br> Residual vector as described above. 154 155 """ 156 measurements = FPEX0setup.Measurements 157 meas_count = len(measurements.rates) 158 meas_values = measurements.values 159 meas_T = measurements.temperatures 160 meas_rates = measurements.rates 161 162 grid_T = FPEX0setup.Grid.gridT 163 164 # simulate and store the FP solution 165 sol = simulate(FPEX0setup, p_all) 166 167 # evaluate at measurement rates 168 simdata = sol.sol(meas_rates) 169 170 resvec = np.empty(1) 171 for k in range(meas_count): 172 # select grid points matching to measurements 173 _, idxGrid, idxMeas = np.intersect1d(grid_T, meas_T[k], assume_unique=True, return_indices=True) 174 if len(idxGrid) != len(meas_T[k]): 175 raise ValueError("Grid does not fit.") 176 # get corresponding measurement and simulation data 177 measVals = meas_values[k][idxMeas] 178 simVals = simdata[idxGrid, k] 179 resvec = np.append(resvec, measVals - simVals) 180 181 return resvec
10def simulate(FPEX0setup, pvec, odeoptions={}, method="BDF"): 11 """ 12 Simulates Fokker-Planck with specified parameters for FP drift, diffusion, and initial function. 13 14 ## Takes 15 **FPEX0setup** 16 <br> An FPEX0 setup configuration (`fpex0.setup.Setup`). 17 18 **p_all** 19 <br> Vector of parameters, coupled to FPEX0setup. 20 <br> It must have the same scheme as FPEX0setup.Parameters.p_0, 21 then simulate() knows how to extract the parameters correctly. 22 Usually this is ensured by the optimizer, who got the initial parameters and changes them through 23 optimization steps. 24 25 **odeoptions** 26 <br> kwargs passed to the scipy solve_ivp ODE solver 27 (https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html). 28 29 **method** 30 <br> The method scipy solve_ivp should use. 31 <br> Default is BDF, an an implicit multistep method. 32 33 34 ## Returns 35 **solution** 36 <br> A scipy solve_ivp bunch object. Important parameters are 37 - t: time points 38 - y: corresponding values 39 - sol: A (here) callable solution 40 41 See link above for details. 42 43 ### Comments 44 The function will not let you set dense_output = False in odeoptions. 45 """ 46 # extract parameters 47 p_FPdrift = FPEX0setup.Parameters.extract_p_FPdrift(pvec) 48 p_FPdiffusion = FPEX0setup.Parameters.extract_p_FPdiffusion(pvec) 49 p_IC = FPEX0setup.Parameters.extract_p_iniDist(pvec) 50 51 # evaluate initial distribution 52 gridT = FPEX0setup.Grid.gridT 53 u0 = FPEX0setup.IniDistFcn(gridT, p_IC) 54 55 # retrieve "time" horizon for integrator 56 t0tf = FPEX0setup.Grid.gridTdot[[0, -1]] 57 58 # generate right hand side, jacobian 59 FPrhs = FPEX0setup.make_rhsFcn(p_FPdrift, p_FPdiffusion) 60 FPjac = FPEX0setup.make_jacFcn(p_FPdrift, p_FPdiffusion) 61 62 # setup integrator and update options, jacobian therein 63 integrator = FPEX0setup.Integration.integrator 64 odeoptions = FPEX0setup.Integration.updateOptions(odeoptions) 65 odeoptions["dense_output"] = True # dense_output for evaluable solution 66 odeoptions = FPEX0setup.Integration.updateJacobian(FPjac) 67 68 69 # start integration 70 try: 71 sTime = time.time() 72 solution = integrator(FPrhs, t0tf, u0, method,**odeoptions) 73 duration = time.time() - sTime 74 print(f"Simulate: {duration:.3f}s") 75 except: 76 exception = sys.exc_info() 77 traceback.print_exception(*exception) 78 print('Integration failed!') 79 80 duration = None 81 solution = None 82 83 return solution
Simulates Fokker-Planck with specified parameters for FP drift, diffusion, and initial function.
Takes
FPEX0setup
An FPEX0 setup configuration (fpex0.setup.Setup
).
p_all
Vector of parameters, coupled to FPEX0setup.
It must have the same scheme as FPEX0setup.Parameters.p_0,
then simulate() knows how to extract the parameters correctly.
Usually this is ensured by the optimizer, who got the initial parameters and changes them through
optimization steps.
odeoptions
kwargs passed to the scipy solve_ivp ODE solver
(https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html).
method
The method scipy solve_ivp should use.
Default is BDF, an an implicit multistep method.
Returns
solution
A scipy solve_ivp bunch object. Important parameters are
- t: time points
- y: corresponding values
- sol: A (here) callable solution
See link above for details.
Comments
The function will not let you set dense_output = False in odeoptions.
86def fit(FPEX0setup, optimizer='lsq'): 87 """ 88 Fits the Fokker-Planck simulation to the given measurements as an optimization of the parameters 89 for drift, diffusion and the initial distribution. 90 91 ## Takes 92 **FPEX0setup** 93 <br> An FPEX0 setup configuration (`fpex0.setup.Setup`). 94 95 **optimizer** 96 <br> The optimizer that should be used. So far only least squares is implemented. 97 98 99 ## Returns 100 **result** 101 <br> A scipy.optimize.OptimizeResult object, with fit.x holding the parameter vector found. 102 103 """ 104 # retrieve parameter values, bounds, indices 105 p_0 = FPEX0setup.Parameters.p0 106 p_lb = FPEX0setup.Parameters.p_lb 107 p_ub = FPEX0setup.Parameters.p_ub 108 109 # set function that computes the residual vector 110 resvecfun = lambda p: residual(FPEX0setup, p) 111 112 # optimization 113 print(f'Running {optimizer}.\n') 114 115 if optimizer.lower() == 'lsq': 116 lsq_opts = {} 117 # set options 118 lsq_opts["jac"] = '3-point' 119 lsq_opts["max_nfev"] = 100000 # max function evaluations 120 # tolerances 121 lsq_opts["xtol"] = 1e-6 # x-step 122 lsq_opts["ftol"] = 1e-10 # function-step 123 lsq_opts["gtol"] = 1.0 # norm of gradient, quite high, but okay for FD 124 125 lsq_opts["x_scale"] = 'jac' # let set scipy set scale with jacobian 126 lsq_opts["verbose"] = 2 # give detailed progress information 127 result = optimize.least_squares(resvecfun, p_0, bounds=(p_lb, p_ub), **lsq_opts) 128 129 return result 130 131 else: 132 raise ValueError("Your specified optimizer is not yet implemented. \nYou are welcomed to contact us by mail if interested in contributing!")
Fits the Fokker-Planck simulation to the given measurements as an optimization of the parameters for drift, diffusion and the initial distribution.
Takes
FPEX0setup
An FPEX0 setup configuration (fpex0.setup.Setup
).
optimizer
The optimizer that should be used. So far only least squares is implemented.
Returns
result
A scipy.optimize.OptimizeResult object, with fit.x holding the parameter vector found.
135def residual(FPEX0setup, p_all): 136 """ 137 Calculates the residual vector of measurements and simulation values, i.e. measVals - simVals 138 at suitable points. 139 140 141 ## Takes 142 **FPEX0setup** 143 <br> An FPEX0 setup configuration (`fpex0.setup.Setup`). 144 145 **p_all** 146 <br> Vector of parameters, coupled to FPEX0setup. 147 <br> It must have the same scheme as FPEX0setup.Parameters.p_0, 148 then residual() knows how to use the parameters correctly. 149 Usually this is ensured by the optimizer, who got the initial parameters and changes them through 150 optimization steps. 151 152 ## Returns 153 **resvec** 154 <br> Residual vector as described above. 155 156 """ 157 measurements = FPEX0setup.Measurements 158 meas_count = len(measurements.rates) 159 meas_values = measurements.values 160 meas_T = measurements.temperatures 161 meas_rates = measurements.rates 162 163 grid_T = FPEX0setup.Grid.gridT 164 165 # simulate and store the FP solution 166 sol = simulate(FPEX0setup, p_all) 167 168 # evaluate at measurement rates 169 simdata = sol.sol(meas_rates) 170 171 resvec = np.empty(1) 172 for k in range(meas_count): 173 # select grid points matching to measurements 174 _, idxGrid, idxMeas = np.intersect1d(grid_T, meas_T[k], assume_unique=True, return_indices=True) 175 if len(idxGrid) != len(meas_T[k]): 176 raise ValueError("Grid does not fit.") 177 # get corresponding measurement and simulation data 178 measVals = meas_values[k][idxMeas] 179 simVals = simdata[idxGrid, k] 180 resvec = np.append(resvec, measVals - simVals) 181 182 return resvec
Calculates the residual vector of measurements and simulation values, i.e. measVals - simVals at suitable points.
Takes
FPEX0setup
An FPEX0 setup configuration (fpex0.setup.Setup
).
p_all
Vector of parameters, coupled to FPEX0setup.
It must have the same scheme as FPEX0setup.Parameters.p_0,
then residual() knows how to use the parameters correctly.
Usually this is ensured by the optimizer, who got the initial parameters and changes them through
optimization steps.
Returns
resvec
Residual vector as described above.