MATLAB: How to properly model a kinetic reaction system from experimental data using lsqcurvefit

kineticslsqcurvefitMATLABparameter estimation

Hello,
I am currently trying to model chemical kinetics using MATLAB.
Currently, I am having a code that works but gives me a set on non-sense results. So there must be a mistake in the method, or in my way of thinking.
I will introduce the problem, the available data and my MATLAB integration. I hope you can help me find the mistakes and obtain a fit for my experimental data.
Thanks in advance.
Problem introduction:
KineticModel.png
This system consists of four reactions. Compound c1 reacts to compound c2. Compound c2 can react further to c3, although I am currently neglecting the reaction of c2 to c3. The challenge is that both c1 and c2 can undergo polymerization reactions to p1 and p2. This of course has an influence on their concentrations and therefore on kinetics. Fitting just reaction 1 (c1 to c2) and neglecting reactions 3 and 4 leads to extreme reaction orders. Also, during experiments the mass balance can never close properly if these reactions are to be neglected.
I have shared my code at the bottom of this question so you can see how I implemented the three reactions into MATLAB.
Available data:
Over a certain time period we have the concentration profiles of compounds c1 and c2. The amount of c3 is negligable, p1 and p2 can not be analyzed.
These profiles look like this:
Profiles.png
This data is figurated however close to the experimental data. It should be well sufficient for this modeling challenge.
MATLAB Integration:
In MATLAB, I have used the lsqcurvefit method as described here: https://nl.mathworks.com/matlabcentral/answers/43439-monod-kinetics-and-curve-fitting. Thanks Star Strider for this starting point. I can now create a code that runs. However, the results from lsqcurvefit do not match the experimental data at all. Moreover, the results are different for every run.
My goal is to plot in figure 2 the experimental data and the model fit. As you will probably see, it is a mess although I am not sure why.
Your time and effort is very much apprieciated.
My code:
clear, clc
%Reaction system
%c1 --> c2 --> c3 (c2 --> c3 for now neglected)
%c1 --> p1
%c2 --> p2
%Reaction rates are described as r = k*c^n
%As c2 --> c3 is found negligable, data is available only for c1 and c2
%The last two reaction equations describe polymerization reactions, there
%is no data available for both p1 and p2.
%Data
time = [10 20 40 60 180];
c1 = [0.508 0.442 0.385 0.354 0.258];
c2 = [0.246 0.301 0.359 0.398 0.489];
c1_0 = 0.711;
c2_0 = 0;
%Plot

time_plot = [0,time];
c1_plot = [c1_0,c1];
c2_plot = [c2_0,c2];
figure(1)
plot(time_plot,c1_plot,'bo')
grid on
hold on
plot(time_plot,c2_plot,'ro')
hold off
xlabel('Time [min]')
ylabel('Concentration [mol/L]')
legend('c1','c2')
%Data processing
x_in = time';
y_in = [c1',c2'];
%Starting point for lsqcurvefit
p0 = [0.1,1,0.1,1,0.1,1];
lb=zeros(6,1);
ub=4*ones(6,1);
%Lsqcurvefit
[p,Rsdnrm,Rsd,ExFlg,OptmInfo,Lmda,Jmat] = lsqcurvefit(@diff1,p0,x_in,y_in,lb,ub);
FitData = diff1(p,time,1);
c1_out = FitData(:,1);
c2_out = FitData(:,2);
%Plot
c1_out_plot = [c1_0,c1_out'];
c2_out_plot = [c2_0,c2_out'];
figure(2)
plot(time_plot,c1_plot,'bo')
grid on
hold on
plot(time_plot,c2_plot,'ro')
plot(time_plot,c1_out_plot,'b')
plot(time_plot,c2_out_plot,'r')
hold off
xlabel('Time [min]')
ylabel('Concentration [mol/L]')
legend('c1','c2')
function C = diff1(p,time,~)
%dc1/dt = -(k1*c1^n1+k3*c1^k3)
%dc2/dt = k1*c1^n1-(k2*c2^k2+k4*c2^n4)
%dc3/dt = k2*c2^n2 = 0
%variables: c1 = x(1), c2 = x(2)
%parameters: k1 = p(1), n1 = p(2), k3 = p(3),
%n3 = p(4), k4 = p(5), n4 = p(6)
%for now we neglect reaction r2 of c2 to c3.
x0 = rand(2,1);
[t,Cout] = ode45(@DifEq, time, x0);
function dC = DifEq(t, x)
xdot = zeros(2,1);
xdot(1) = -(p(1).*x(1).^p(2)+p(3).*x(1)^p(4));
xdot(2) = p(1).*x(1).^p(2)-p(5).*x(2).^p(6);
dC = xdot;
end
C = Cout;
end

Best Answer

Thank you for referring to my ’Monod Kinetics’ Answer! I’m glad you found it useful!
There are two problems with your code.
First, ‘xdot(1)’ should be:
xdot(1) = -(p(1).*x(1).^p(2)-p(3).*x(1)^p(4));
with the sign of the second term being negative (corrected here).
Second, it is a good idea to always include the initial values as parameters, as a rule, the last parameters. So here:
p0 = [0.1,1,0.1,1,0.1,1,c1(1),c2(1)];
and then incorporating those changes in ‘diff1’:
function C = diff1(p,time,~)
%dc1/dt = -(k1*c1^n1+k3*c1^k3)
%dc2/dt = k1*c1^n1-(k2*c2^k2+k4*c2^n4)
%dc3/dt = k2*c2^n2 = 0
%variables: c1 = x(1), c2 = x(2)
%parameters: k1 = p(1), n1 = p(2), k3 = p(3),
%n3 = p(4), k4 = p(5), n4 = p(6)
%for now we neglect reaction r2 of c2 to c3.
x0 = p(7:8);
[t,Cout] = ode45(@DifEq, time, x0);
function dC = DifEq(t, x)
xdot = zeros(2,1);
xdot(1) = -(p(1).*x(1).^p(2)-p(3).*x(1)^p(4));
xdot(2) = p(1).*x(1).^p(2)-p(5).*x(2).^p(6);
dC = xdot;
end
C = Cout;
end
the estimated parameters are:
p =
0.105408507696630
3.999984376449200
0.000078501021266
0.007282181170239
0.002202264694465
3.902023967475535
0.502316713470111
0.244896706878439
and your model fits your data almost exactly!