ODE event callback function does not update the solution vector

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

Accepted Answer

Stephen23
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
ans = -1
nargout(@mywrapper) % function handle to a function
ans = 2
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:
  1 Comment
Julian
Julian on 13 May 2025
Thank you for your help! I managed to get it working with a wrapper function, I then passed my object as a parameter. I now have it as follows, the callback function is in a wrapper and all other parameters passed to the ode function are set as parameters.
if obj.predictors == 1
E = odeEvent('EventFcn',@obj.predictorUpdateCheck, ...
'Direction',"descending", ...
'Response',"callback", ...
'CallbackFcn',@updateWrapper);
else
E = [];
end
% setup ode
odeFun = @(t,x,p) obj.odefunMO(t,x,p);
ODE = ode(ODEFcn=odeFun,InitialValue=x0(:),EventDefinition=E,Solver="ode45");
ODE.Parameters = {wb, tspan(2), obj};
sol = solve(ODE,tspan(1),tspan(2));
I use the following wrapper function:
function [stop, xupdated] = updateWrapper(t,x,i,p)
obj = p{1,3};
[stop, xupdated] = obj.predictorUpdate(t,x,[],p);
end

Sign in to comment.

More Answers (0)

Products


Release

R2024b

Community Treasure Hunt

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

Start Hunting!