setting state variable to a given value using ode15s - matlab

I'm using ode15s to simulate/solve a set of ODEs. I would like to implement a feature, where upon reaching a given condition during the simulation, a number in the model changes programatically (e.g., an indicator constant) for a fixed amount of time, and then reverts back.
This could be, for example using Lotka-Volterra equations:
dx/dt = alphax - betax*y
dy/dt = (delta+indicator)xy - gammay + epsilonindicator
indicator starts as 0. Let's say that when x reaches 10, I'd like to switch indicator to 1 for 10 time units, and then flip it back to 0.
This can be done in a dirty way by using global variables, however, this is something I'd like to avoid (impossible to parallelize + general avoidance of global variables). Is there a neat alternative way when using ode15s (i.e., I don't know the time step)?
Many thanks for any suggestions!

Edit: As noted correctly by LutzL, wrapping an ODE with non-smooth state without handling events may lead to inaccurate results
as you can not predict at what time points in what order the ODE
function is evaluated. LutzL
So the accurate solution is to deal with ODE events. An example for the modified Lotka-Volterra equations is given below, where the event fires, if x gets >10 and the indicator will be turned on for 10 seconds:
% parameters and times:
params = ones(5,1); % [alpha, ..., epsilon]
z_start = [2, 1];
t_start = 0;
t_end = 30;
options = odeset('Events',#LotkaVolterraModEvents); % set further options here, too.
% wrap ODE function with indicator on and off
LVModODE_indicatorOn = #(t,z)LotkaVolterraModODE(t,z,1, params);
LVModODE_indicatorOff = #(t,z)LotkaVolterraModODE(t,z,0, params);
% storage for simulation values:
data.t = t_start;
data.z = z_start;
data.teout = [];
data.zeout = zeros(0,2);
data.ieout = [];
% temporary loop variables:
z_0 = z_start;
t_0 = t_start;
isIndicatorActive = false;
while data.t(end) < t_end % until the end time is reached
if isIndicatorActive
% integrate for 10 seconds, if indicator is active
active_seconds = 10;
[t, z, te,ze,ie] = ode15s(LVModODE_indicatorOn, [t_0 t_0+active_seconds], z_0, options);
else
% integrate until end or event, if indicator is not active.
[t, z, te,ze,ie] = ode15s(LVModODE_indicatorOff, [t_0 t_end], z_0, options);
isIndicatorActive = true;
end
%append data to storage
t_len = length(t);
data.t = [data.t; t(2:end)];
data.z = [data.z; z(2:end,:)];
data.teout = [data.teout; te];
data.zeout = [data.zeout; ze];
data.ieout = [data.ieout; ie];
% reinitialize start values for next iteration of loop
t_0 = t(end);
z_0 = z(end, :);
% set the length of the last instegration
options = odeset(options,'InitialStep',t(end) - t(end-1));
end
%% plot your results:
figure;
plot(data.t, data.z(:,1), data.t, data.z(:,2));
hold all
plot(data.teout, data.zeout(:,1), 'ok');
legend('x','y', 'Events in x')
%% Function definitions for integration and events:
function z_dot = LotkaVolterraModODE(t, z, indicator, params)
x = z(1); y= z(2);
% state equations: modified Lotka-Volterra system
z_dot = [params(1)*x - params(2)*y;
(params(4) + indicator)*x*y - params(3)*y + params(5)*indicator];
end
function [value, isTerminal, direction] = LotkaVolterraModEvents(t,z)
x = z(1);
value = x-10; % event on rising edge when x passes 10
isTerminal = 1; %stop integration -> has to be reinitialized from outer logic
direction = 1; % only event on rising edge (i.e. x(t_e-)<10 and x(t_e+)>10)
end
The main work is done in the while loop, where the integration takes place.
(Old post) The following solution may lead to inaccurate results, handling events, as explained in the first part should be preferred.
You could wrap your problem in a class, which is able to hold a state (i.e. its properties). The class should have a method, which is used as odefun for the variable-step integrator. See also here on how to write classes in MATLAB.
The example below demonstrates, how it could be achieved for the example you provided:
% file: MyLotkaVolterra.m
classdef MyLotkaVolterra < handle
properties(SetAccess=private)
%define, if the modified equation is active
indicator;
% define the start time, where the condition turned active.
tStart;
% ode parameters [alpha, ..., epsilon]
params;
end
methods
function self = MyLotkaVolterra(alpha, beta, gamma, delta, epsilon)
self.indicator = 0;
self.tStart = 0;
self.params = [alpha, beta, gamma, delta, epsilon];
end
% ODE funciton for the state z = [x;y] and time t
function z_dot = odefun(self, t, z)
x = z(1); y= z(2);
if (x>=10 && ~self.indicator)
self.indicator = 1;
self.tStart = t;
end
%condition to turn indicator off:
if (self.indicator && t - self.tStart >= 10)
self.indicator = false;
end
% state equations: modified Lotka-Volterra system
z_dot = [self.params(1)*x - self.params(2)*y;
(self.params(4) + self.indicator)*x*y - ...
self.params(3)*y + self.params(5)*self.indicator];
end
end
end
This class could be used as follows:
% your ode using code:
% 1. create an object (`lvObj`) from the class with parameters alpha = ... = epsilon = 1
lvObj = MyLotkaVolterra(1, 1, 1, 1, 1);
% 2. pass its `odefun`method to the integrator (exaple call with ode15s)
[t,y] = ode15s(#lvObj.odefun, [0,5], [9;1]); % 5 seconds

Related

I can't fix the problem of my PID simulation code(I think it's about difference of the differentiator)

I'm trying to build a PID controller, its differential part multiply by a low-pass filter, witch transform func is 1/(r*s+1) (r = 4*Ts ).
I use forward difference to discrete the controller, and use state space function to handle the object, I want a step response of this system.
And I used pid() feedback() step() functions to check if I'm right.
And yet I was wrong, here is the response.
I tried only PI control.
You can see it's almost perfect. So I think the problem is about differential. But I really can't find it out. I already have checked the forward difference many times.
I tried backward difference and got same curve.
T = 0.05;
Cp = 6;
Ci = 1;
Cd = 7;
P = zeros(1, n+1);
I = zeros(1, n+1);
D = zeros(1, n+1);
r = 4*T;
for i = 1:n
if i == 1
e(i) = U(i)-0; % error of time domain, U is step function
P(i) = Cp*e(i);
I(i) = 0;
D(i) = 0;
else
e(i) = U(i)-y(i-1);
P(i) = Cp*e(i);
I(i) = Ci*T*e(i-1)+I(i-1); % forward
D(i) = (Cd*(e(i)-e(i-1))-D(i-1)*(T-r))/r; % forward
% I(i) = T*Ci*e(i)+I(i-1); % backward
% D(i) = (Cd*(e(i)-e(i-1))+r*D(i-1))/(r+T); % backward
% I(i) = (T*(e(i)+e(i-1))+2*I(i-1))/2; % Bilinear
end
u(i) = P(i) + I(i) + D(i);
x(:,i+1) = x(:,i)+T*(Ao*x(:,i)+Bo*u(i));
y(i) = Co*x(:,i);
end
% this how I get the right curve.
PIDF = pid(Cp, Ci, Cd, r);
fb = feedback(PIDF*G_sysc, 1); % G_sysc is trans func of object.
step(fb, N(1:n)*T, 'blue');
I expect should be almost same with the Step Response.
Alright... I have fingered why it's different out by myself. Because the function feedback() set the error as 0 at the first point, and next point, the error would be step function - output = 1, so the differential will be a big positive, that cause the output convergence quicker than my PID controller.
Now I only want to know how do I edit the pid() and feedback() functions to set the first error to be 1, cause this is more common sense.

MATLAB: Entering multiple input values for the x-axis in a graph

The MATLAB program below is a function that references specific input values for S, E, r, sigma, and tau.
function [C, Cdelta, P, Pdelta] = ch08(S,E,r,sigma,tau)
% Input arguments: S = asset price at time t
% E = Exercise price
% r = interest rate
% sigma = volatility
% tau = time to expiry (T-t)
%
% Output arguments: C = call value, Cdelta = delta value of call
% P = Put value, Pdelta = delta value of put
%
% function [C, Cdelta, P, Pdelta] = ch08(S,E,r,sigma,tau)
if tau > 0
d1 = (log(S/E) + (r + 0.5*sigma^2)*(tau))/(sigma*sqrt(tau));
d2 = d1 - sigma*sqrt(tau);
N1 = 0.5*(1+erf(d1/sqrt(2)));
N2 = 0.5*(1+erf(d2/sqrt(2)));
C = S*N1-E*exp(-r*(tau))*N2;
Cdelta = N1;
P = C + E*exp(-r*tau) - S;
Pdelta = Cdelta - 1;
title('Graph of Call Value vs. Time to Expiry')
xlabel('Time to expiry')
ylabel('Call Value')
plot (tau,C)
else
C = max(S-E,0);
Cdelta = 0.5*(sign(S-E) + 1);
P = max(E-S,0);
Pdelta = Cdelta - 1;
title('Graph of Call Value vs. Time to Expiry')
xlabel('Time to expiry')
ylabel('Call Value')
plot (tau,C)
end
After running
ch08(3,2.5,0.03,0.25,1)
The following output is produced
After running the function again,
ch08(3,2.5,0.03,0.25,1)
hold on
ch08(3,2.5,0.03,0.25,0.9)
Two data points are produced
After manually typing decreasing tau values,
ch08(3,2.5,0.03,0.25,1)
hold on
ch08(3,2.5,0.03,0.25,0.9)
hold on
ch08(3,2.5,0.03,0.25,0.8)
hold on
ch08(3,2.5,0.03,0.25,0.7)
hold on
ch08(3,2.5,0.03,0.25,0.6)
hold on
ch08(3,2.5,0.03,0.25,0.5)
hold on
ch08(3,2.5,0.03,0.25,0.4)
hold on
ch08(3,2.5,0.03,0.25,0.3)
hold on
ch08(3,2.5,0.03,0.25,0.2)
hold on
ch08(3,2.5,0.03,0.25,0.1)
The graph will produce a bunch of data points,
Is there a way to automate the tau values entered in ch08(S,E,r,sigma,tau) so that the user doesn't have to type all of the input in?
As I suggested in comments, you need to use a for loop. You can create an array with values of tau that you want to use, and call your function with a different element from that array in each loop iteration:
figure, hold on
tau = 10.^(0:-1:-6);
for ii = 1:length(tau)
ch08(3,2.5,0.03,0.25,tau(ii))
end
However, a better solution would be to not plot within your ch08 function, and return the value C as you did in your first version of your question. Then you can do this:
tau = 10.^(0:-1:-6);
C = zeros(size(tau));
for ii = 1:length(tau)
C(ii) = ch08(3,2.5,0.03,0.25,tau(ii));
end
plot(tau,C,'.');
This would allow you to change your plot as you please, for example drawing a line through your points.
PS: Note that you only need to give hold on once. It sets a flag in the figure window that is not cleared until you do hold off or clf.

Executing nested functions with a timer object

I am trying to write a program which acquires an image from a webcam, averages over several of the vertical pixel rows, displays the result and then applies a Gaussian fit to locate the peak position with a high degree of accuracy. All of this is to be done in real time. I used a timer object to run the function (called datain) which triggers the camera and acquires the data. To iterate and update the data the function includes a while loop. My code worked perfectly until I added a command inside the while loop to call another function (called GaussianFit) which preformed the fit. When this function is added I receive an error which says the callback function in the timer does not have enough inputs.
"Error while evaluating TimerFcn for timer 'timer-1' Not enough input arguments."
This seems odd since the data acquisition function provides all the inputs needed by the fitting function. My code is as follows:
function WaveAQ()
global webcamin
%%%%%%%%%%%%%%%%%%%%%%%
% Create video object %
%%%%%%%%%%%%%%%%%%%%%%%
dID = 2; vForm = 'MJPG_1280x720';
% Search for existing video objects to avoid creating multiple handles
vObj = imaqfind('DeviceID', dID, 'VideoFormat', vForm);
if isempty(vObj)
webcamin = videoinput('winvideo',dID,vForm);
else
webcamin = vObj{1};
end
% Configure # datasets to be averaged per measurement
set(webcamin,'FramesPerTrigger',1);
% Repeat until stopped
set(webcamin,'TriggerRepeat',Inf);
% Set to grayscale
set(webcamin,'ReturnedColorSpace','grayscale');
% Allow webcam to be triggered by timer
triggerconfig(webcamin,'manual')
% % View the properties for the selected video source object.
% src_obj = getselectedsource(webcamin);
% get(src_obj)
% Prompt for dark reading
j = 1;
j = input('Would you like to take a dark reading? (y/n): ','s');
switch j
case 'y'
j = 1;
case 'n'
j = 2;
end
if j == 1
prompt = input('Block input and enter any number to continue: ');
end
% We now define a function which will acquire data from the camera to be
% executed by the timer function
fig1 = figure(1);
function datain(~,event,input)
persistent image
h = 1; % Initialize Loop
bg = 0;
avgroi = 0;
% Calibration and fitting constants
tol = 1;
calib_slope = .003126;
calib_offset = 781.8;
spect = (1:1280)*calib_slope + calib_offset;
while h == 1
% Check if figure is open
h = ishandle(fig1);
trigger(input);
% First run finds dark reading
if j == 1
bg = getdata(input,1,'uint8');
j = 2;
else
image = getdata(input,1,'uint8');
image = image - bg; % Correct for background
npxy = length(image(:,1));
npxx = length(image(1,:));
% Region of interest for spectral data
dy_roi = 200;
y_roi = npxy/2;
% Region of interest for beam position
dx_roi = npxx/2;
x_roi = npxx/2;
avgroi = sum(image((y_roi-dy_roi):(y_roi+dy_roi),:))/(2*dy_roi);
avgroi = avgroi/256; % Normalize units
avg_y = (sum(image(:,(x_roi-dx_roi+1):(x_roi+dx_roi))).'/(2*dx_roi)).';
[cx,sx,Ipeak] = GaussianFit(avgroi,tol);
% cx = max(avgroi);
% sx = 2;
% Ipeak = max(avgroi);
subplot(2,1,1)
plot(spect,avgroi)
title('Spectrogram')
xlabel('Wavelength (nm)')
ylabel('Intensity (arb.)')
legend(['\lambda_P_e_a_k = ',num2str(cx),'nm'])
subplot(2,1,2)
plot(1:length(avg_y),avg_y/256)
title('Beam Profile in Y')
xlabel('Pixel #')
ylabel('Intensity (arb.)')
end
end
end
%%%%%%%%%%%%%%%%%%%
% Define timer object to execute daq %
%%%%%%%%%%%%%%%%%%%
% Set callback properies
timerfcn = #datain;
% Image acquisition rate
Raq = 5;
timer_exec = timer('TimerFcn',{timerfcn,webcamin},'period',1/Raq,'BusyMode','drop');% Maybe change busy mode to queue if regular acquisition interval needed.
% timer_exec2 = timer('TimerFcn',#GaussianFit,'period',1/Raq,'BusyMode','drop');
% Execute acquistion
start(webcamin);
preview(webcamin);
start(timer_exec);
% start(timer_exec2);
% Clean system
stop(timer_exec);
% stop(timer_exec2);
delete(timer);
stop(webcamin);
delete(webcamin);
clear functions;
end
Can anyone tell me why the timer needs more inputs when GaussianFit is called inside the loop?

Solving PDE with Matlab

`sol = pdepe(m,#ParticleDiffusionpde,#ParticleDiffusionic,#ParticleDiffusionbc,x,t);
% Extract the first solution component as u.
u = sol(:,:,:);
function [c,f,s] = ParticleDiffusionpde(x,t,u,DuDx)
global Ds
c = 1/Ds;
f = DuDx;
s = 0;
function u0 = ParticleDiffusionic(x)
global qo
u0 = qo;
function [pl,ql,pr,qr] = ParticleDiffusionbc(xl,ul,xr,ur,t,x)
global Ds K n
global Amo Gc kf rhop
global uavg
global dr R nr
sum = 0;
for i = 1:1:nr-1
r1 = (i-1)*dr; % radius at i
r2 = i * dr; % radius at i+1
r1 = double(r1); % convert to double precision
r2 = double(r2);
sum = sum + (dr / 2 * (r1*ul+ r2*ur));
end;
uavg = 3/R^3 * sum;
ql = 1;
pl = 0;
qr = 1;
pr = -((kf/(Ds.*rhop)).*(Amo - Gc.*uavg - ((double(ur/K)).^2).^(n/2) ));`
dq(r,t)/dt = Ds( d2q(r,t)/dr2 + (2/r)*dq(r,t)/dr )
q(r, t=0) = 0
dq(r=0, t)/dr = 0
dq(r=dp/2, t)/dr = (kf/Ds*rhop) [C(t) - Cp(at r = dp/2)]
q = solid phase concentration of trace compound in a particle with radius dp/2
C = bulk liquid concentration of trace compound
Cp = trace compound concentration at particle surface
I want to solve the above pde with initial and boundary conditions given. Tried Matlab's pdepe, but does not work satisfactorily. Maybe the boundary conditions is creating problem for me. I also used this isotherm equation for equilibrium: q = K*Cp^(1/n). This is convection-diffusion equation but i could not find any write ups that addresses solving this type of equation properly.
There are two problems with the current implementation.
Incorrect Source Term
The PDE you are attempting to solve has the form
which has the equivalent form
where the last term arises due to the factor of 2 in the original PDE.
The last term needs to be incorporated into pdepe via a source term.
Calculation of q average
The current implementation attempts to calculate the average value of q using the left and right values of q passed to the boundary condition function.
This is incorrect.
The average value of q needs to be calculated from a vector of up-to-date values of the quantity.
However, we have the complication that the only function to receive all mesh values is ParticleDiffusionpde; however, the mesh values passed to that function are not guaranteed to be from the mesh we provided.
Solution: use events (as described in the pdepe documentation).
This is a hack since the event function is meant to detect zero-crossings, but it has the advantage that the function is given all values of q on the mesh we provide.
So, the working example below (you'll notice I set all of the parameters to 1 since I didn't know better) uses the events function to update a variable qStore that can be accessed by the boundary condition function (see here for an explanation), and the boundary condition function performs a vectorized trapezoidal integration for the average calculation.
Working Example
function [] = ParticleDiffusion()
% Parameters
Ds = 1;
q0 = 0;
K = 1;
n = 1;
Amo = 1;
Gc = 1;
kf = 1;
rhop = 1;
% Space
rMesh = linspace(0,1,10);
rMesh = rMesh(:) ;
dr = rMesh(2) - rMesh(1) ;
% Time
tSpan = linspace(0,1,10);
% Vector to store current u-value
qStore = zeros(size(rMesh));
options.Events = #(m,t,x,y) events(m,t,x,y);
% Solve
[sol,~,~,~,~] = pdepe(1,#ParticleDiffusionpde,#ParticleDiffusionic,#ParticleDiffusionbc,rMesh,tSpan,options);
% Use the events function to update qStore
function [value,isterminal,direction] = events(m,~,~,y)
qStore = y; % Value of q on rMesh
value = m; % Since m is constant, it will never be zero (no event detection)
isterminal = 0; % Continue integration
direction = 0; % Detect all zero crossings (not important)
end
function [c,f,s] = ParticleDiffusionpde(r,~,~,DqDr)
% Define the capacity, flux, and source
c = 1/Ds;
f = DqDr;
s = DqDr./r;
end
function u0 = ParticleDiffusionic(~)
u0 = q0;
end
function [pl,ql,pr,qr] = ParticleDiffusionbc(~,~,R,ur,~)
% Calculate average value of current solution
qL = qStore(1:end-1);
qR = qStore(2: end );
total = sum((qL.*rMesh(1:end-1) + qR.*rMesh(2:end))) * dr/2;
qavg = 3/R^3 * total;
% Left boundary
pl = 0;
ql = 1;
% Right boundary
qr = 1;
pr = -(kf/(Ds.*rhop)).*(Amo - Gc.*qavg - (ur/K).^n);
end
end

Matlab Event Finder is occasionally missing events

I've been working on a numerical model that has been giving me some trouble lately. I'm attempting to solve a time series for a system where there is an impact at a certain location. Obviously Matlab's event finder is for exactly this sort of situation, but it's failing me, in that it occasionally crosses the event threshold without triggering.
My system is a pendulum with a wall at -30 degrees. Should be pretty simple; solve until pendulum hits -30, stop, reverse velocity, etc. However, as you can see in my picture, it occasionally just swings right past -30 and doesn't stop integrating. The code is mostly a copy-paste job of the ball bounce example with the differential equation changed, but somehow it's not working properly. A photo of the problematic time series is here:
and my code is here: https://gist.github.com/f892a764ca10027a3b89
function matlab_timeser
k1 = .557;
tstart = 0;
tfinal = 1200;
refine = 100;
y0 = [-3.0*pi/180.0; 6.0*pi/180.0];
options = odeset('Events',#events,'Refine',refine);
tout = tstart;
yout = y0.'; % <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
teout = [];
yeout = [];
ieout = [];
while max(tout) < tfinal
[t,y,te,ye,ie] = ode113(#f,[tstart tfinal],y0,options);
% Accumulate output.
nt = length(t);
tout = [tout; t(2:nt)];
yout = [yout; y(2:nt,:)];
teout = [teout; te]; % Events at tstart are never reported.
yeout = [yeout; ye];
ieout = [ieout; ie];
% Set the new initial conditions, with k attenuation.
y0(1) = -30 * pi/180;
y0(2) = -k1*y(nt,2);
% A good guess of a valid first time step is the length of
% the last valid time step, so use it for faster computation.
options = odeset(options,'InitialStep',t(nt)-t(nt-refine),'MaxStep',t(nt)-t(1));
tstart = t(nt);
% plot(tout,yout(:,1)*180/pi)
% hold on
% plot([0 t(nt)],[-30 -30],'r')
% hold off
% pause()
end
plot(tout,yout(:,1)*180/pi)
hold on
plot([0 t(nt)],[-30 -30],'r')
end
function dydt = f(t, y)
wf = 0.8008 * 2 * pi;
param = [6e-4 6.087 2.1e-3 0.236 0.557];
coul1 = param(1);
w1 = param(2);
z1 = param(3);
AL = param(4);
dydt = [y(2); AL*(wf)^2*sin(wf*t)*cos(y(1))-(w1)^2*sin(y(1))-2.0*z1*w1*y(2)-coul1*((y(2)^2)+(w1)^2*cos(y(1)))*sign(y(2))];
end
function [value,isterminal,direction] = events(t,y)
value = y(1) + 30*pi/180; % Detect height = -30
isterminal = 1; % Stop the integration
direction = 0; % any direction
end
I've tried a few different ode solvers and while they give slightly different results, they all seem to miss events here and there.
If anyone has any insights into why Event may occasionally fail to notice an event, I'd appreciate it! Thanks!