Global fit coupled odes system lmfit - global

I'm trying to get the global fit of multiple set of data considering a system of 4 coupled ODEs.
I have the working code that solves the system of 4 coupled ODEs for a single set of data, and I have the working code that do the global fit with an arbitrary function (but not using odeint).
My problem is that I'm not able to merge the two codes...
Code for coupled ODEs
t =
data=
def gauss(x, amp, sigma, center):
"""Gaussian lineshape."""
return amp * np.exp(-(x-center)**2 / (2.*sigma**2))
def f(xs, t, ps):
"""Lotka-Volterra predator-prey model."""
try:
amp = ps['amp'].value
center = ps['center'].value
sigma = ps['sigma'].value
T1 = ps['T1'].value
Teq = ps['Teq'].value
except Exception:
amp, center, sigma, T1, Teq = ps
s0,s1,s2,s3 = xs
return [- gauss(t,amp,sigma,center) * (s0-s1),\
gauss(t,amp,sigma,center) * (s0-s1) - s1/T1,\
(s1/T1 - s2/Teq),\
(s2/Teq)]
def g(t, x0, ps):
x = odeint(f, x0, t, args=(ps,))
return x
def residual(ps, ts, data):
x0 = ps['s0'].value, ps['s1'].value, ps['s2'].value, ps['s3'].value
b = ps['b'].value
model = (((g(ts, x0, ps)[:,0]-g(ts, x0, ps)[:,1]+g(ts, x0, ps)[:,2]+b*g(ts, x0, ps)[:,3]))**2)/((g(ts, x0, ps)[0,0]))**2
return (model - data).ravel()
# set parameters incluing bounds
params = Parameters()
params.add('s0', value=1, vary=False)
params.add('s1', value=0, vary=False)
params.add('s2', value=0, vary=False)
params.add('s3', value=0, vary=False)
params.add('amp', value=0.02)
params.add('center', value=5)
params.add('sigma', value=0.1)
params.add('T1', value=0.3)
params.add('Teq', value=0.7)
params.add('b', value=-1)
# fit model and find predicted values
result = minimize(residual, params, args=(t, data), method='leastsq')
final = data + result.residual.reshape(data.shape)
Considering the code here: https://lmfit.github.io/lmfit-py/examples/example_fit_multi_datasets.html
I've tried to do by myself the code for global fit in this case
def gauss(x, amp, sigma, center):
"""Gaussian lineshape."""
return amp * np.exp(-(x-center)**2 / (2.*sigma**2))
def f(xs, t, ps):
"""Lotka-Volterra predator-prey model."""
try:
amp = ps['amp'].value
center = ps['center'].value
sigma = ps['sigma'].value
T1 = ps['T1'].value
Teq = ps['Teq'].value
except Exception:
amp, center, sigma, T1, Teq = ps
s0,s1,s2,s3 = xs
return [- gauss(t,amp,sigma,center) * (s0-s1),\
gauss(t,amp,sigma,center) * (s0-s1) - s1/T1,\
(s1/T1 - s2/Teq),\
(s2/Teq)]
def g(t, x0, params):
"""
Solution to the ODE x'(t) = f(t,x,k) with initial condition x(0) = x0
"""
x = odeint(f, x0, t, args=(params,))
return x
def testmodel(params, ts, data):
x0 = params['s0'].value, params['s1'].value, params['s2'].value, params['s3'].value
b = params['b'].value
model = (((g(ts, x0, params)[:,0]-g(ts, x0, params)[:,1]+g(ts, x0, params)[:,2]+b*g(ts, x0, params)[:,3]))**2)/((g(ts, x0, params)[0,0]))**2
return model
def testmodel_dataset(params, i, x):
"""Calculate Gaussian lineshape from parameters for data set."""
x0 = params[f's0_{i+1}'], params[f's1_{i+1}'], params[f's2_{i+1}'], params[f's3_{i+1}']
amp = params[f'amp_{i+1}']
center = params[f'center_{i+1}']
sigma = params[f'sigma_{i+1}']
T1 = params[f'T1_{i+1}']
Teq = params[f'Teq_{i+1}']
b = params[f'b_{i+1}']
return testmodel(params, x, data)
def objective(params, x, data):
"""Calculate total residual for fits of Gaussians to several data sets."""
ndata, _ = data.shape
resid = 0.0*data[:]
# make residual per data set
for i in range(ndata):
resid[i, :] = data[i, :] - testmodel_dataset(params, i, x)
# now flatten this to a 1D array, as minimize() needs
return resid.flatten()
fit_params = Parameters()
for iy, y in enumerate(data):
fit_params.add(f's0_{iy+1}', value=1)
fit_params.add(f's1_{iy+1}', value=0)
fit_params.add(f's2_{iy+1}', value=0)
fit_params.add(f's3_{iy+1}', value=0)
fit_params.add(f'amp_{iy+1}', value=0.5)
fit_params.add(f'center_{iy+1}', value=0.5)
fit_params.add(f'sigma_{iy+1}', value=0.5)
fit_params.add(f'T1_{iy+1}', value=0.5)
fit_params.add(f'Teq_{iy+1}', value=0.4)
fit_params.add(f'b_{iy+1}', value=0.3)
for iy in (2, 3, 4, 5, 6):
fit_params[f'sigma_{iy}'].expr = 'sigma_1'
out = minimize(objective, fit_params, args=(x, data))
report_fit(out.params)
Result -> KeyError: 's0'
There is a problem with x0 and s0,s1,s2,s3, population of the four states.
I'm sorry if the question may be very naive...
Thank you for your help.

Related

Solving a 4 ODE system in MATLAB using ode45

I am not very used to MATLAB and I'm trying to solve the following problem using MATLAB ode45, however, it's not working.
I was working on a problem in reaction engineering, using a Semi-Batch Reactor.
The reaction is given by
A + B ---> C + D
A is placed in the reactor and B is being continuously added into the reactor with a flowrate of v0 = 0.05 L/s. Initial volume is V0 = 5 L. The reaction is elementary. The reaction constant is k = 2.2 L/mol.s.
Initial Concentrations: for A: 0.05 M, for B: 0.025 M.
Performing a mole balance of each species in the reactor, I got the following 4 ODEs, and the expression of V (volume of the reactor is constantly increasing)
Solving this system and plotting the solution against time, I should get this
Note that plots of C(C) and C(D) are the same.
And let's set tau = v0/V.
Now for the MATLAB code part.
I have searched extensively online, and from what I've learned, I came up with the following code.
First, I wrote the code for the ODE system
function f = ODEsystem(t, y, tau, ra, y0)
f = zeros(4, 1);
f(1) = ra - tau*y(1);
f(2) = ra + tau*(y0(2) - y(2));
f(3) = -ra - tau*y(3);
f(4) = -ra - tau*y(4);
end
Then, in the command window,
t = [0:0.01:5];
v0 = 0.05;
V0 = 5;
k = 2.2;
V = V0 + v0*t;
tau = v0./V;
syms y(t);
ra = -k*y(1)*y(2);
y0 = [0.05 0.025 0 0];
[t, y] = ode45(#ODEsystem(t, y, tau, ra, y0), t, y0);
plot(t, y);
However, I get this...
Please if anyone could help me fix my code. This is really annoying :)
ra should not be passed as parameter but be computed inside the ODE system. V is likewise not a constant. Symbolic expressions should be used for formula transformations, not for numerical methods. One would also have to explicitly evaluate the symbolic expression at the wanted numerical values.
function f = ODEsystem(t, y, k, v0, V0, cB0)
f = zeros(4, 1);
ra = -k*y(1)*y(2);
tau = v0/(V0+t*v0);
f(1) = ra - tau*y(1);
f(2) = ra + tau*(cB0 - y(2));
f(3) = -ra - tau*y(3);
f(4) = -ra - tau*y(4);
end
Then use the time span of the graphic, start with all concentrations zero except for A, use the concentration B only for the inflow.
t = [0:1:500];
v0 = 0.05;
V0 = 5;
k = 2.2;
cB0 = 0.025;
y0 = [0.05 0 0 0];
[t, y] = ode45(#(t,y) ODEsystem(t, y, k, v0, V0, cB0), t, y0);
plot(t, y);
and get a good reproduction of the reference image

"Inner matrix dimensions must agree" - but it shouldn't be a matrix

The following is a snippet from a function I am defining:
function [V1, V2] = lambert(R1, R2, t, string)
r1 = norm(R1);
r2 = norm(R2);
z = -100;
while F(z,t) < 0
z = z + 0.1;
end
tol = 1.e-8;
nmax = 5000;
ratio = 1;
n = 0;
while (abs(ratio) > tol) & (n <= nmax)
n = n + 1;
ratio = F(z,t)/dFdz(z);
z = z - ratio;
end
f = 1 - y(z)/r1;
g = A*sqrt(y(z)/mu);
gdot = 1 - y(z)/r2;
V1 = (R2-f*R1)/g;
V2 = (gdot*R1-R1)/g;
function dum = y(z)
dum = r1 + r2 + A*(z*S(z) - 1)/sqrt(C(z));
end
When I run this code with some vectors R1, R2, I get:
Error using *.
Inner matrix dimensions must agree.
This occurs at these lines:
V1 = (R2-f*R1)/g;
V2 = (gdot*R1-R1)/g;
I think this is due to the fact that f and gdot and g are matrices whose dimensions are incompatible with R1 and R2. However, as I have defined them, they should simply be scalars. I do not understand what in the code causes them to be matrices.
I've tried using .* instead of * etc, but with no success.

How can I implement a low-pass Butterworth filter in Matlab?

From this answer, I know how to create a High-pass Butterworth filter.
From this video, I know that, lowpasskernel = 1 - highpasskernel.
So, I created the following Low-pass Butterworth Filter,
function [out, kernel] = butterworth_lp(I, Dl, n)
height = size(I, 1);
width = size(I, 2);
[u, v] = meshgrid(-floor(width/2):floor(width/2)-1,-floor(height/2):floor(height/2)-1);
% lp_kernel = 1 - hp_kernel
kernel = 1 - butter_hp_kernel(u, v, Dl, n);
% fft the image
I_fft_shifted = fftshift(fft2(double(I)));
% apply lowpass filter
I_fft_shift_filtered = I_fft_shifted .* kernel;
% inverse FFT, get real components
out = real(ifft2(ifftshift(I_fft_shift_filtered)));
% normalize and cast
out = (out - min(out(:))) / (max(out(:)) - min(out(:)));
out = uint8(255*out);
function k = butter_hp_kernel(u, v, Dh, n)
uv = u.^2+v.^2;
D = sqrt(uv);
frac = Dh./D;
p = 2*n;
denom = frac.^p;
k = 1./denom;
Output
This isn't a low-pass filter output.
So, what is the issue with my code?
You didn't correctly copy the formula in the high pass filter:
uv = u.^2+v.^2;
D = uv.^0.5;
frac = Dh./D;
p = 2*n;
denom = frac.^p;
k = 1./(1+denom); --> you forgot the "1+"
New output:
Okay. I have solved the problem by following the following formula (Page #8/48),
Output
Source Code
butter_lp_kernel.m
function f = butter_lp_f(u, v, Dl, n)
uv = u.^2+v.^2;
Duv = sqrt(uv);
frac = Duv./Dl;
denom = frac.^(2*n);
f = 1./(1.+denom);
function k = butter_lp_kernel(I, Dl, n)
Height = size(I,1);
Width = size(I,2);
[u, v] = meshgrid( ...
-floor(Width/2) :floor(Width-1)/2, ...
-floor(Height/2): floor(Height-1)/2 ...
);
k = butter_lp_f(u, v, Dl, n);
ifftshow.m
function out = ifftshow(f)
f1 = abs(f);
fm = max(f1(:));
out = f1/fm;
end
butterworth_lpf.m
function [out1, out2] = butterworth_lpf(I, Dl, n)
Kernel = butter_lp_kernel(I, Dl, n);
I_ffted_shifted = fftshift(fft2(I));
I_ffted_shifted_filtered = I_ffted_shifted.*Kernel;
out1 = ifftshow(ifft2(I_ffted_shifted_filtered));
out2 = ifft2(ifftshift(I_ffted_shifted_filtered));
end

How can I fit a cosine function?

I wrote a python function to get the parameters of the following cosine function:
param = Parameters()
param.add( 'amp', value = amp_guess, min = 0.1 * amp_guess, max = amp_guess )
param.add( 'off', value = off_guess, min = -10, max = 10 )
param.add( 'shift', value = shift_guess[0], min = 0, max = 2 * np.pi, )
fit_values = minimize( self.residual, param, args = ( azi_unique, los_unique ) )
def residual( self, param, azi, data ):
"""
Parameters
----------
Returns
-------
"""
amp = param['amp'].value
off = param['off'].value
shift = param['shift'].value
model = off + amp * np.cos( azi - shift )
return model - data
In Matlab how can get the amplitude, offset and shift of the cosine function?
My experience tells me that it's always good to depend as little as possible on toolboxes. For your particular case, the model is simple and doing it manually is pretty straightforward.
Assuming that you have the following model:
y = B + A*cos(w*x + phi)
and that your data is equally-spaced, then:
%// Create some bogus data
A = 8;
B = -4;
w = 0.2;
phi = 1.8;
x = 0 : 0.1 : 8.4*pi;
y = B + A*cos(w*x + phi) + 0.5*randn(size(x));
%// Find kick-ass initial estimates
L = length(y);
N = 2^nextpow2(L);
B0 = (max(y(:))+min(y(:)))/2;
Y = fft(y-B0, N)/L;
f = 5/(x(2)-x(1)) * linspace(0,1,N/2+1);
[A0,I] = max( 2*abs(Y(1:N/2+1)) );
w0 = f(I);
phi0 = 2*imag(Y(I));
%// Refine the fit
sol = fminsearch(#(t) sum( (y(:)-t(1)-t(2)*cos(t(3)*x(:)+t(4))).^2 ), [B0 A0 w0 phi0])
Results:
 
sol = %// B was -4 A was 8 w was 0.2 phi was 1.8
-4.0097e+000 7.9913e+000 1.9998e-001 1.7961e+000
MATLAB has a function called lsqcurvefit in the optimisation toolbox:
lsqcurvefit(fun,X0,xdata,ydata,lbound,ubound);
where fun is the function to fit, x0 is the initial parameter guess, xdata and ydata are self-explanatory, and lbound and ubound are the lower and upper bounds to the parameters. So, for instance, you might have a function:
% x(1) = amp
% x(2) = shift
% x(3) = offset
% note cosd instead of cos, because your data appears to be in degrees
cosfit = #(x,xdata) x(1) .* cosd(xdata - x(2)) + x(3);
You would then call the lsqcurvefit function as follows:
guess = [7,150,0.5];
lbound = [-10,0,-10]
ubound = [10,360,10]
fit_values = lsqcurvefit(cosfit,guess,azi_unique,los_unique,lbound,ubound);

Implement Fitzhugh-Nagumo model via Crank-Nicolson

For a problem, I need to implement the Fitzhugh-Nagumo model with spatial diffusion via Crank-Nicolson's scheme. Now the problem lays withing the spatial diffusion.
(V_{t}) (DV_{xx} + V(V-a)(1-V) - W + I)
(W_{t}) (epsilon(V - b*W )
whereas DV_{xx} is the spatial diffusion.
Using Matlab, the following function can be given to i.e. an ODE45 solver. However it does not yet implement the spatial diffusion...
function dy = FHN( t, Y, D, a, b, eps, I )
V = Y(1);
W = Y(2);
dY = zeros(2,1);
% FHN-model w/o spatial diffusion
Vxx = 0;
dY(0) = D .* Vxx + V .* (V-a) .* (1-V) - W + I;
dY(1) = eps .* (V-b .* W);
The question: How to implement V_{xx} ?
Besides, what matrix shape does V need to be? Normally V is depending only on t, and is thus a [1 by t] vector. Now V is depending on both x as t, thus i would expect it to be a [x by y] vector, correct?
Thank you
It took long, but hey its not a normal everyday problem.
function f = FN( t, Y, dx, xend, D, a, b, eps, I )
% Fitzhug-Nagumo model with spatial diffusion.
% t = Tijd
% X = [V; W]
% dx = stepsize
% xend = Size van x
% Get the column vectors dV and dW from Y
V = Y( 1:xend/dx );
W = Y( xend/dx+1:end );
% Function
Vxx = (V([2:end 1])+V([end 1:end-1])-2*V)/dx^2;
dVdt = D*Vxx + V .* (V-a) .* (1-V) - W + I ;
dWdt = epsilon .* (V-b*W);
f = [dVdt ; dWdt];
Both V as W are column vectors with a size of 1:(xend/dx)
Method of calling:
V = zeros(xend/dx,1);
W = zeros(xend/dx,1);
% Start Boundaries
% V(x, 0) = 0.8 for 4 < x < 5
% V(x, 0) = 0.1 for 3 < x < 4
V( (4/dx+1):(5/dx-1) ) = 0.8;
V( (3/dx+1):(4/dx-1) ) = 0.1;
Y0 = [V; W];
t = 0:0.1:400;
options = '';
[T, Y] = ode45( #FN, t, Y0, options, dx, xend, D, a, b, eps, I );