I am working on a project right now where I have modeled the motion of a multibody mechanical system. It is a complex, non-smooth system, and the motion of the system is not always described by the same set of equations, which means if I solve this numerically I have to stop the integration at times (and start it up again with new initial conditions and equations).
Now I would like to implement this in code using Matlab's event functions. The concept is easy, but as always the documentation is a bit nondescriptive when it comes to implementing it for a real problem. (There is a example in the documentation, but I cannot see the source code for some reason). I have nonetheless made some code, now I am just stuck at how to put it all together: I want the ODE-solver (e.g. ode45) to solve my equations, then stop when a event occurs, and begin to integrate again with a new set of ODEs and initial condtions, but I don't know how to pass the ODE-solver what equations to solve next and what the new intital conditions are!
Code:
function dy = firstODE(t,y)
dy = zeros(2,1);
dy(1) = y(2);
dy(2) = 1/(A1)*(B1-c*y(1));
end
function dy = secondODE(t,y)
dy = zeros(4,1);
dy(1) = y(2);
dy(2) = -c/A2*y(1);
dy(3) = y(4);
dy(4) = D2/B2 + c*C2/(A2*B2)*y(1);
end
function [value,isterminal,direction] = myEventsFcn(t,y)
value = [y(1)-K1, y(1)+K1, y(1)+K2, y(1)+K1, y(1)-K1];
isterminal = [1, 1, 1, 1, 1];
direction = [-1, -1, -1, 1, 1];
end
How do I tell the ODE-solver what new initial conditions and equations to use after an event occurs?
Call two different solutions. i.e.
sol1 = ode45(#(t,x)firstODE(t,x),tspan1,init1);
% set some conditions
sol2 = ode45(#(t,x)secondODE(t,x),tspan2,init2);
If you want tspan2 to start where the previous one left off and go to the end of your originally defined tspan1, set tspan2 = [sol1.xe,tspan1(end)].
Related
I am trying to solve two coupled reaction diffusion equations in 1d, using pdpe, namely
$\partial_t u_1 = \nabla^2 u_1 + 2k(-u_1^2+u_2)$
$\partial_t u_2 = \nabla^2 u_1 + k(u_1^2-u_2)$
The solution is in the domain $x\in[0,1]$, with initial conditions being two identical Gaussian profiles centered at $x=1/2$. The boundary conditions are absorbing for both components, i.e. $u_1(0)=u_2(0)=u_1(1)=u_2(1)=0$.
Pdepe gives me a solution without prompting any errors. However, I think the solutions must be wrong, because when I set the coupling to zero, i.e. $k=0$ (and also if I set it to be very small, say $k=0.001$), the solutions do not coincide with the solution of the simple diffusion equation
$\partial_t u = \nabla^2 u$
as obtained from pdepe itself.
Strangely enough, the solutions $u_1(t)=u_2(t)$ from the "coupled" case with coupling set to zero, and the solution for the case uncoupled by construction $u(t')$ coincide if we set $t'=2t$, that is, the solution of the "coupled" case evolves twice as fast as the solution of the uncoupled case.
Here's a minimal working example:
Coupled case
function [xmesh,tspan,sol] = coupled(k) %argument is the coupling k
std=0.001; %width of initial gaussian
center=1/2; %center of gaussian
xmesh=linspace(0,1,10000);
tspan=linspace(0,1,1000);
sol = pdepe(0,#pdefun,#icfun,#bcfun,xmesh,tspan);
function [c,f,s] = pdefun(x,t,u,dudx)
c=ones(2,1);
f=zeros(2,1);
f(1) = dudx(1);
f(2) = dudx(2);
s=zeros(2,1);
s(1) = 2*k*(u(2)-u(1)^2);
s(2) = k*(u(1)^2-u(2));
end
function u0 = icfun(x)
u0=ones(2,1);
u0(1) = exp(-(x-center)^2/(2*std^2))/(sqrt(2*pi)*std);
u0(2) = exp(-(x-center)^2/(2*std^2))/(sqrt(2*pi)*std);
end
function [pL,qL,pR,qR] = bcfun(xL,uL,xR,uR,t)
pL=zeros(2,1);
pL(1) = uL(1);
pL(2) = uL(2);
pR=zeros(2,1);
pR(1) = uR(1);
pR(2) = uR(2);
qL = [0 0;0 0];
qR = [0 0;0 0];
end
end
Uncoupled case
function [xmesh,tspan,sol] = uncoupled()
std=0.001; %width of initial gaussian
center=1/2; %center of gaussian
xmesh=linspace(0,1,10000);
tspan=linspace(0,1,1000);
sol = pdepe(0,#pdefun,#icfun,#bcfun,xmesh,tspan);
function [c,f,s] = pdefun(x,t,u,dudx)
c=1;
f = dudx;
s=0;
end
function u0 = icfun(x)
u0=exp(-(x-center)^2/(2*std^2))/(sqrt(2*pi)*std);
end
function [pL,qL,pR,qR] = bcfun(xL,uL,xR,uR,t)
pL=uL;
pR=uR;
qL = 0;
qR = 0;
end
end
Now, suppose we run
[xmesh,tspan,soluncoupled] = uncoupled();
[xmesh,tspan,solcoupled] = coupled(0); %coupling k=0, i.e. uncoupled solutions
One can directly check by plotting the solutions for any time index $it$ that, even if they should be identical, the solutions given by each function are not identical, e.g.
hold all
plot(xmesh,soluncoupled(it+1,:),'b')
plot(xmesh,solcoupled(it+1,:,1),'r')
plot(xmesh,solcoupled(it+1,:,2),'g')
On the other hand, if we double the time of the uncoupled solution, the solutions are identical
hold all
plot(xmesh,soluncoupled(2*it+1,:),'b')
plot(xmesh,solcoupled(it+1,:,1),'r')
plot(xmesh,solcoupled(it+1,:,2),'g')
The case $k=0$ is not singular, one can set $k$ to be small but finite, and the deviations from the case $k=0$ are minimal, i.e. the solution still goes twice as fast as the uncoupled solution.
I really don't understand what is going on. I need to work on the coupled case, but obviously I don't trust the results if it does not give the right limit when $k\to 0$. I don't see where I could be making a mistake. Could it be a bug?
I found the source of the error. The problem lies in the qL and qR variables of bcfun for the coupled() function. The MATLAB documentation, see here and here, is slightly ambiguous on whether the q's should be matrices or column vectors. I had used matrices
qL = [0 0;0 0];
qR = [0 0;0 0];
but in reality I should have used column vectors
qL = [0;0];
qR = [0;0];
Amazingly, pdpe didn't throw an error, and simply gave wrong results. This should perhaps be fixed by the developers.
The issue I have is having to compute the derivative (in real time) of the solution produced by ode45 within the events function.
Some pseudo-code to explain what I'm mean is,
function dx = myfunc(t,x,xevent,ie)
persistent xs
persistent dx2
% xevent is the solution at the event
if isempty(dx2) == 1
dx2 = 0;
end
k = sign(dx2*x(1));
if ie(end) == 1
xs = xevent
elseif ie(end) == 2
xs = xevent
end
dx(1) = x(2);
dx(2) = function of k,x(1),x(2), and xs;
dx2 = dx(2);
end
function [value,isterminal,direction] = myeventfcn(t,x)
% dx2 = some function of x
if dx2*x(1)>=0
position = [dx2*x(1); dx2*x(1)];
isterminal = [1; 1];
direction = [1 ; -1 ];
elseif dx2*x(1)<0
position = [dx2*x(1); dx2*x(1)];
isterminal = [1; 1];
direction = [-1 ; 1 ];
end
I know that if I didn't need to use the solution at the event within myfunc I could just compute dx=myfunc(t,x) within my event function and use dx(2), yet since xevent is used within myfunc I can't input xevent.
I know there is a way of inputting constant parameters within the event function, but since it's the solution at the event location, which also changes, I'm not sure how to go about doing that.
My work around to this is to approximate dx(2) using the solution x. What I wanted to know is if it will be satisfactory to just use a finite difference approximation here, using a small fixed step size relative to the step size od45 takes, which is a variable step size.
As a note, the reason I have myeventfcn split by the if statement is to know what the direction the event is crossed, since it will update within myfunc.
Also, I need to use dx(2) of the previous successful time step and so that's why I have dx2 defined as a persistent variable. I believe having k=sign(dx(2)*x(1)) is okay to do, since my event depends on dx(2)*x(1), and so I won't be introducing any new discontinuities with the sign function.
Thanks for any help!
I'm trying to model the motion of a particle in a potential and incorporate feedback. The problem I'm having is adding a delay to the feedback. So, working in Matlab, I have a basic script which looks like:
clear all; close all;
qx = .3;
qy = .4;
qz = .5;
om_D = 1.2e8;
ep = 1e-10;
t = 0:1e-11:1e-5;
[t,rss]=ode45(#ion_test, t, [1e-7 .45 2e-7 -.45 0.5e-7 .45],[], qx, qy, qz, om_D, ep);
where ion_test.m is a function file which reads as follows:
function dr = ion_test(t,r, qx, qy, qz, om_D, ep)
dr = zeros(6,1);
dr(1) = r(2);
dr(2) = (2*qx*cos(om_D.*t) + ep.*2*r(3)*r(5));
dr(3) = r(4);
dr(4) = (2*qy*cos(om_D.*t) + ep.*2*r(1)*r(5));
dr(5) = r(6);
dr(6) = (2*qz*cos(om_D.*t) + ep.*2*r(1)*r(3));
end
So as you can see, the differential equations for each of the coordinates of r are coupled. The thing which I need to add is the ability for each equation to call upon the values of other coordinates at previous times, not just their current values. The purpose of this is to build in a delayed feedback mechanism.
Any help very much appreciated!
I think the answer to my question was basically that I needed to use dde23 instead of ode45.
I'm running a set of ODEs with ode45 in MATLAB and I need to save one of the variables (that's not the derivative) for later use. I'm using the function 'assignin' to assign a temporary variable in the base workspace and updating it at each step. This seems to work, however, the size of the array does not match the size of the solution vector acquired from ode45. For example, I have the following nested function:
function [Z,Y] = droplet_momentum(theta,K,G,P,zspan,Y0)
options = odeset('RelTol',1e-7,'AbsTol',1e-7);
[Z,Y] = ode45(#momentum,zspan,Y0,options);
function DY = momentum(z,y)
DY = zeros(4,1);
%Entrained Total Velocity
Ve = sin(theta)*(y(4));
%Total Relative Velocity
Urs = sqrt((y(1) - y(4))^2 + (y(2) - Ve*cos(theta))^2 + (y(3))^2);
%Coefficients
PSI = K*Urs/y(1);
PHI = P*Urs/y(1);
%Liquid Axial Velocity
DY(1) = PSI*sign(y(1) - y(4))*(1 + (1/6)*(abs(y(1) - y(4))*G)^(2/3));
%Liquid Radial Velocity
DY(2) = PSI*sign(y(2) - Ve*cos(theta))*(1 + (1/6)*(abs(y(2) - ...
Ve*cos(theta))*G)^(2/3));
%Liquid Tangential Velocity
DY(3) = PSI*sign(y(3))*(1 + (1/6)*(abs(y(3))*G)^(2/3));
%Gaseous Axial Velocity
DY(4) = (1/z/y(4))*((PHI/z)*sign(y(1) - y(4))*(1 + ...
(1/6)*(abs(y(1) - y(4))*G)^(2/3)) + Ve*Ve - y(4)*y(4));
assignin('base','Ve_step',Ve);
evalin('base','Ve_out(end+1) = Ve_step');
end
end
In the above code, theta (radians), K (negative value), P, & G are constants and for the sake of this example can be taken as any value. Zspan is just the integration time step for the ODE solver and Y0 is the initial conditions vector (4x1). Again, for the sake of this example these can take any reasonable value. Now in the main file, the function is called with the following:
Ve_out = 0;
[Z,Y] = droplet_momentum(theta,K,G,P,zspan,Y0);
Ve_out = Ve_out(2:end);
This method works without complaint from MATLAB, but the problem is that the size of Ve_out is not the same as the size of Z or Y. The reason for this is because MATLAB calls the ODE function multiple times for its algorithm, so the solution is going to be slightly smaller than Ve_out. As am304 suggested, I could just simply calculated DY by giving the ode function a Z and Y vector such as DY = momentum(Z,Y), however, I need to get this working with 'assignin' (or similar method) because another version of this problem has an implicit dependence between DY and Ve and it would be too computationally expensive to calculate DY at every iteration (I will be running this problem for many iterations).
Ok, so let's start off with a quick example of an SSCCE:
function [Z,Y] = khan
options = odeset('RelTol',1e-7,'AbsTol',1e-7);
[Z,Y] = ode45(#momentum,[0 12],[0 0],options);
end
function Dy = momentum(z,y)
Dy = [0 0]';
Dy(1) = 3*y(1) + 2* y(2) - 2;
Dy(2) = y(1) - y(2);
Ve = Dy(1)+ y(2);
assignin('base','Ve_step',Ve);
evalin('base','Ve_out(end+1) = Ve_step;');
assignin('base','T_step',z);
evalin('base','T_out(end+1) = T_step;');
end
By running [Z,Y] = khan as the command line, I get a complete functional code that demonstrates your problem, without all the headaches associated. My patience for this has been exhausted: live and learn.
This seems to work, however, the size of the array does not match the
size of the solution vector acquired from ode45
Note that I added two lines to your code which extracts time variable. From the command prompt, one simply has to run the following to understand what's going on:
Ve_out = [];
T_out = [];
[Z,Y] = khan;
size (Z)
size (T_out)
size (Ve_out)
plot (diff(T_out))
ans =
109 1
ans =
1 163
ans =
1 163
Basically ode45 is an iterative algorithm, which means it will regularly course correct (that's why you regularly see diff(T) = 0). You can't force the algorithm to do what you want, you have to live with it.
So your options are
1. Use a fixed step algorithm
2. Have a function call that reproduces what you want after the ode45 algorithm has done its dirty work. (am304's solution)
3. Collects the data with the time function, then have an algorithm parse through everything to removes the extra data.
Can you not do something like that? Obviously check the sizes of the matrices/vectors are correct and amend the code accordingly.
[Z,Y] = droplet_momentum2(theta,K,G,P,zspan,Y0);
DY = momentum(Z,Y);
Ve = sin(theta)*(0.5*z*DY(4) + y(4));
i.e. once the ODE is solved, computed the derivative DY as a function of Z and Y (which have just been solved by the ODE) and finally Ve.
I'm experimenting with ode45 in Matlab. I've learned how to pass parameters to the ode function but I still have a question. Let's suppose that I want to compute the trajectory (speed profile) of a Car and I have a function, e.g. getAcceleration, that gives me the acceleration of the car but also the right gear: [acceleration, gear] = getAcceleration(speed,modelStructure) where modelStructure represents the model of the car.
The ode function would be:
function [dy] = car(t,y,modelStructure)
dy = zeros(2,1);
dy(1) = y(2);
[dy(2),gear] = getAcceleration(y(1),modelStructure);
Then I call the Ode45 integrator in this way:
tInit = 0;
tEnd = 5,
[t,y] = ode45(#car,[tInit tEnd], [speedInitial,accelerationInitial],options,modelStructure);
The problem is: how do I get the vector storing gears? Should I have something like [t,y,gear]=ode45(....) or should gear be within the y vector?
I've been working on my code and using the events function I'm now able to get the car 'gears' changes (as events).
Now I have a new problem related to the same code.
Imagine that when I evaluate de 'dy' vector I'm able to get a further value Z which let me to have a massive speed up calling the acceleration computation (getAcceleration):
function [dy] = car(t,y,modelStructure)
dy = zeros(2,1);
dy(1) = y(2);
[dy(2),Z(t)] = getAcceleration(y(1),modelStructure,Z(t-1));
and suppose that I'm also able to compute Z at the initial condition. The problem is that I'm not able to compute the Z derivative.
Is there a way to pass Z value throw the stepping without integrating it?
Thanks guys.
First off: why are the initial values to the differential equation the initial speed (speedInitial) and the initial acceleration (accelerationInitial)? That means that the differential equation car will be computing the acceleration (y(1)) and the jerk (y(2)), the time-derivative of the acceleration, at each time t. That seems incorrect...I would say the initial values should be the initial position (positionInitial) and the initial speed (speedInitial). But, I don't know your model, I could be wrong.
Now, getting the gear in the solution directlty: you can't, not without hacking ode45. This is also logical; you also cannot get dy at all times directly, can you? That's just not how ode45 is set up.
There's two ways out I see here:
Global variable
DISCLAIMER: don't use this method. It's only here to show what most people would do as a first attempt.
You can store gear in a global variable. It's probably the least amount of coding, but also the least convenient outcome:
global ts gear ii
ii = 1;
tInit = 0;
tEnd = 5,
[t,y] = ode45(...
#(t,y) car(t,y,modelStructure), ...
[tInit tEnd], ...
[speedInitial, accelerationInitial], options);
...
function [dy] = car(t,y,modelStructure)
global ts gear ii
dy = zeros(2,1);
dy(1) = y(2);
[dy(2),gear(ii)] = getAcceleration(y(1),modelStructure);
ts(ii) = t;
ii = ii + 1;
But, due to the nature of ode45, this will get you an array of times ts and associated gear which contains intermediate points and/or points that got rejected by ode45. So, you'll have to filter for those afterwards:
ts( ~ismember(ts, t) ) = [];
I'll say it again: this is NOT the method I'd recommend. Only use global variables when testing or doing some quick-n-dirty stuff, but always very quickly shift towards other solutions. Also, the global variables grow at each (sub-)iteration of ode45, which is an unacceptable performance penalty.
It's better to use the next method:
Post-solve call
This is also not too hard for your case, and the way I'd recommend you to go. First, modify the differential equation as below, and solve as normal:
tInit = 0;
tEnd = 5,
[t,y] = ode45(...
#(t,y) car(t,y,modelStructure), ...
[tInit tEnd], ...
[speedInitial, accelerationInitial], options);
...
function [dy, gear] = car(t,y,modelStructure)
dy = [0;0];
dy(1) = y(2);
[dy(2),gear] = getAcceleration(y(1),modelStructure);
and then after ode45 completes, do this:
gear = zeros(size(t));
for ii = 1:numel(t)
[~, gear(ii)] = car(t(ii), y(ii,:).', modelStructure);
end
That will get you all the gears the car would have at times t.
The only drawback that I can see here is that you'll have many more function evaluations of car than ode45 would use by itself. But this is only a real problem if each evaluation of car takes in the order of seconds or longer, which I suspect is not the case in your setup.