Figure A.10:

Fit of model to measurements using estimated parameters.

Figure A.10

Code for Figure A.10

Text of the GNU GPL.

ABC_data.dat


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
   0.000   1.035   0.008   0.020
   0.263   0.636   0.393   0.034
   0.526   0.368   0.480   0.165
   0.789   0.214   0.499   0.327
   1.053   0.137   0.457   0.433
   1.316   0.079   0.422   0.531
   1.579   0.049   0.310   0.579
   1.842   0.038   0.284   0.693
   2.105   0.060   0.185   0.772
   2.368   0.005   0.200   0.851
   2.632   0.008   0.141   0.843
   2.895  -0.037   0.098   0.896
   3.158   0.026   0.105   0.909
   3.421  -0.005   0.042   0.907
   3.684  -0.033   0.088   0.940
   3.947  -0.008   0.013   0.977
   4.211  -0.032   0.025   0.953
   4.474   0.008   0.012   0.954
   4.737  -0.000   0.026   0.984
   5.000   0.006   0.001   0.979

main.py


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# [depends] ABC_data.dat
# This py-file loads data file 'ABC_data.dat'.
from paresto import Paresto
from misc import gnuplotsave
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
import os
plt.ion()

model = Paresto()
model.print_level = 1
model.transcription = 'shooting'
model.nlp_solver_options = {'ipopt': {'mumps_scaling': 0}}
model.x = ['ca', 'cb', 'cc']
model.p = ['k1', 'k2']
model.d = ['m_ca', 'm_cb', 'm_cc']

# Define the massbal_ode function
def massbal_ode(t, x, p):
    r1 = p.k1 * x.ca
    r2 = p.k2 * x.cb
    xdot = [-r1, r1 - r2, r2]
    return xdot

ncase = 1

if ncase == 1:
    # fit A,B,C
    def lsq(t, y, p):
        return [y.ca - y.m_ca, y.cb - y.m_cb, y.cc - y.m_cc]
    nmeas = [0, 1, 2]
elif ncase == 2:
    # fit A only
    def lsq(t, y, p):
        return [y.ca - y.m_ca]
    nmeas = [0]
elif ncase == 3:
    # fit B only
    def lsq(t, y, p):
        return [y.cb - y.m_cb]
    nmeas = [1]
elif ncase == 4:
    # fit C only
    def lsq(t, y, p):
        return [y.cc - y.m_cc]
    nmeas = [2]
else:
    raise ValueError('Invalid ncase')

# Set up the model
tfinal = 6
nplot = 75
tplot = np.linspace(0, tfinal, nplot)

tabledat = np.loadtxt('ABC_data.dat')

# Extract the first column
tmeas = tabledat[:, 0]
ymeas = tabledat[:, 1:4]

combined_times = np.concatenate((tmeas, tplot))
tplot, ic = np.unique(combined_times, return_inverse=True)
meas_ind = ic[:len(tmeas)]

# Step 2: Interpolating Measurements
interpolator = interp1d(tmeas, ymeas, axis=0, kind='previous', fill_value='extrapolate')
y_noisy = interpolator(tplot)

# Step 3: Handling NaN Values
y_noisy[np.isnan(y_noisy)] = 0.0
y_noisy = np.reshape(y_noisy.T,(3,-1,1))


model.ode = massbal_ode
model.lsq = lsq
model.tout = tplot
model.nsets = 1
model.lsq_ind = meas_ind

# Initialize Paresto instance
model.initialize()
xsim, _ = model.simulate({'k1': 0.5, 'k2': 3.0, 'ca': 1.0, 'cb': 0.0, 'cc': 0.0})

# Initial conditions
ca0 = 1.0
cb0 = 0.0
cc0 = 0.0

model.ic.k1 = 0.5
model.ic.k2 = 3.0
model.ic.ca = ca0
model.ic.cb = cb0
model.ic.cc = cc0

model.lb_ub()
model.lb.k1 = 1e-4
model.lb.k2 = 1e-4
model.ub.k1 = 10
model.ub.k2 = 10

est, yy, pp = model.optimize(y_noisy)
conf = model.confidence(est, 0.95)
print('Estimated parameters')
print('\n'.join(f'{k}: {float(v)}' for k, v in est.theta.items()))

print('Bounding box intervals')
print('\n'.join(f'{k}: {float(v)}' for k, v in conf.bbox.items()))


if os.getenv('OMIT_PLOTS') != 'true':
    # Plot the model fit to the noisy measurements
    plt.figure()
    plt.plot(model.tout, est.x[nmeas, :].reshape(-1, len(tplot)).T)
    plt.plot(tmeas, ymeas[:, nmeas], 'o')
    plt.xlabel('Time')
    plt.ylabel('Values')
    plt.title('Model Fit to Noisy Measurements')
    plt.show()

    if ncase != 1:
        plt.figure()
        plt.plot(model.tout, est.x.reshape(-1, len(tplot)).T)
        plt.plot(tmeas, ymeas, 'o')
        plt.xlabel('Time')
        plt.ylabel('Values')
        plt.title('Model Fit for All Measurements')
        plt.show()

# Save model fit (ind 0) and raw measurements (ind 1) so gnuplot can
# pull both with "ind 0" / "ind 1" — matches Octave's `save ABC.dat tableest tabledat`.
tableest = np.column_stack((model.tout, est.x.reshape(-1, len(tplot)).T))
gnuplotsave('ABC.dat', {'tableest': tableest, 'tabledat': tabledat})