fpex0.fpex0 API documentation

# fpex0.fpex0

View Source

      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

def simulate(FPEX0setup, pvec, odeoptions={}, method='BDF'): View Source

    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.

def fit(FPEX0setup, optimizer='lsq'): View Source

     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.

def residual(FPEX0setup, p_all): View Source

    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.