ODE event callback function does not update the solution vector
15 views (last 30 days)
Show older comments
I am solving an ODE that simulates predictors
that predict the value of n sensors. Each sensor has a timer
, when this timer runs out the predictor is updated by using setting it to the actual sensor reading. A timer running out (reaching zero) is also used as the event definition, the timer is then reset and the predictor value is updated. The timer, predictor and sensor values are all part of the solution vector x. All functions (ode, event, callback) are part of a single class. The state/solution vector is not updating when an event triggers. The cause is the way the nargout function handles the event and callback functions. Below I will place all relevant code and explain more, first the ode definition:


if obj.predictors == 1
E = odeEvent('EventFcn',@(t,x) obj.predictorUpdateCheck(t,x), ...
'Direction',"descending", ...
'Response',"callback", ...
'CallbackFcn',@(t,x) obj.predictorUpdate(t,x,[],[]));
else
E = [];
end
% setup ode
odeFun = @(t,x) obj.odefunMO(wb,t,x,tspan(2));
ODE = ode(ODEFcn=odeFun,InitialValue=x0(:),EventDefinition=E,Solver="ode45");
sol = solve(ODE,tspan(1),tspan(2));
The following event function is used:
function minTimer = predictorUpdateCheck(obj,t,x)
x = reshape(x,obj.sys.nx,1,[]);
timers = x(:,:,1);
minTimer = min(timers);
sensorToUpdate = find(timers==minTimer);
if minTimer < 0
fprintf("Sensor %d requires update at %2.2f [s]\n",sensorToUpdate,t)
end
end
The solution vector x is a 3D array, where each slice along the third dimension is a different 'state'. The first layer stores the timers, the second the predictors, etc. The callback function is as follows:
function [stop, xupdated] = predictorUpdate(obj,t,x,i,parameters)
stop = false; % do not stop the simulation
% extract timers and states etc
x = reshape(x,obj.sys.nx,1,[]);
timers = x(:,:,1);
xSys = x(:,:,2);
yhat = x(:,:,3);
y = obj.sys.C*xSys + obj.u(t) + obj.attack.value(t) + obj.noise.value(t);
% find the sensor that needs updating and update
minTimer = min(timers);
sensorToUpdate = find(timers==minTimer);
yhat(sensorToUpdate) = y(sensorToUpdate);
timers(sensorToUpdate) = obj.interSampleTimes(sensorToUpdate,1);
% update the state vector
xupdated = x;
xupdated(:,:,1) = timers;
xupdated(:,:,3) = yhat;
xupdated = xupdated(:);
fprintf("Sensor %d updated at %2.2f\n",sensorToUpdate,t)
end
I do need (some of) the data in the obj to calculate the state update, so taking it out of the class is not really an option. When running this code, the event function recognises that there should be an update and the print statement prints the string. The output is as follows:
Sensor 2 requires update at 0.02 [s]
Sensor 2 updated at 0.01
Sensor 2 requires update at 0.02 [s]
Sensor 2 requires update at 0.01 [s]
Sensor 2 requires update at 0.05 [s]
....... and so on
Within the odeEvent function the callback is handled by the execCallbackFcn:
function [varargout] = execCallbackFcn(obj,varargin)
% Permissively execute the callback function with respect to
% the number of inputs and outputs.
% [stop,ye,parameters] = callbackFcn(te,ye,ie,parameters)
% nargout of this function will always be 3. varargout{3} will
% be [] when parameters are not used (length(varargin) < 4).
if isempty(obj.CallbackFcn)
warning(message('MATLAB:ode:NoCallbackFcn',sprintf('%g',varargin{1})));
Nout = 0;
else
Nin = nargin(obj.CallbackFcn);
if Nin == -1
% Pass all inputs to varargin callback.
Nin = nargin - 1;
end
Nout = nargout(obj.CallbackFcn);
[varargout{1:Nout}] = obj.CallbackFcn(varargin{1:Nin});
end
if Nout == 0
% Default value of stop is true.
varargout{1} = true;
end
if Nout < 2
% ye value passes through unchanged.
varargout{2} = varargin{2};
end
if Nout < 3
if nargin >= 5
% Parameters value passes through unchanged.
varargout{3} = varargin{4};
else
% Return [] for unused Parameters value.
varargout{3} = [];
end
end
end
Nout is set to -1 by the nargout function, while it should be 2. The execCallbackFcn then does not process the update and the solver continues.
So to conclude my question: is there any other way of specifying the event and callback function such that the nargout function correctly determines the number of output arguments and the odeEvent processes the solution vector update?
0 Comments
Accepted Answer
Stephen23
on 13 May 2025
Edited: Stephen23
on 13 May 2025
"The cause is the way the nargout function handles the event and callback functions."
The cause is because you are testing anonymous functions:
"is there any other way of specifying the event and callback function such that the nargout function correctly determines the number of output arguments and the odeEvent processes the solution vector update?"
Yes: replace the anonymous function with a simple function handle. Use one or more wrapper functions if required.
nargout(@(y,z)myfun(y,z,[],[])) % anonymous function
nargout(@mywrapper) % function handle to a function
function [b,x] = myfun(a,b,c,d)
b=false;
x=pi;
end
function [b,x] = mywrapper(a,b)
[b,x] = myfun(a,b,[],[]);
end
As far as I can tell, the EventFcn does not need any anonymous function at all:
'EventFcn',@obj.predictorUpdateCheck
I suspect that a much more robust and simpler approach to what you are trying to achieve is to pass/store a flag.
See also:
More Answers (0)
See Also
Categories
Find more on Ordinary Differential Equations in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!