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

44 views (last 30 days)
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

Accepted Answer

Star Strider
Star Strider on 2 Jul 2019
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!
  4 Comments
John456
John456 on 5 Jul 2019
Hi,
Thanks for elaborating on the points I mentioned.
So then we are both on the same page, using the equation:
xdot(1) = -p(1).*x(1).^p(2)-p(3).*x(1)^p(4);
That's good.
  1. Yes, I mentioned. These two last datapoints do not seem off in any way, so I found it strange that the curvefit stopped telling me it reached its maximum amount of iterations or function evaluations. However, if I now use your results as my estimates, I can solve the system very quickly and find parameters for the system including the 120 and 180 points. Guess I need to pay more attention at what is going in there.
  2. That worked perfectly at first try, thanks!
  3. This as well.
What I want to do is step-wise change my estimates and boundaries to values that are more likely to occur in practise. We are not going to find extreme reaction orders, for example, at least not for the reaction c1 --> c2. When decreasing the upper limit for this reaction order, I obtain, of course, different parameters and a lower coefficient of determination. Like you mentioned, there is no use in trying to obtain a perfect fit from experimental data. On the other hand, I would like use the coefficient of determination to get a fast idea of how well a fit still fits the data when I start changing the boundaries.
I will look shortly into the ga method. For now I decided to include the 0 points because all the fits I would obtain for different sets of data and equations would never be able to properly predict the concentrations at time 0 within a reasonable range.
Lastly, what we have accomplished now with lsqcurvefit works. We can obtain a fit, read parameters, and re-evaluate our parameters by changing boundaries. I think hereby you have answered not only my question, but also helped me to quickly develop my code into something useful. Thanks a lot.
Star Strider
Star Strider on 5 Jul 2019
As always, my pleasure!
I had some problems yesterday getting ga to converge successfully. I intend to keep working on it, and if successful, will post the relevant code and results to a Comment here.

Sign in to comment.

More Answers (0)

Products


Release

R2018b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!