Set up nonlinear continuous- and discrete-time simulations quickly in MATLAB.
Handle complex states easily.
Log and analyze throughout.
The odehybrid
library for MATLAB makes it easy to create simulations of dynamical systems with both continuous-time components (such as physical models) and multiple discrete-time components (such as digital controllers and signal processors). It's like MATLAB's built-in ode45
function, but extended for discrete-time systems, complex state organization, and logging.
It's open source and free to use even on commercial projects.
We have some quick examples below, and the documentation has a plethora of examples, including all features of odehybrid
.
For a stripped-down example, let's simulate an unstable, continuous-time system with a discrete-time stabilizing controller running at 10Hz for 5 seconds.
ode = @(t, x, u) [0 1; 2 0] * x + [0; 1] * u; % Differential equation
de = @(t, x, u) deal(x, -[8 4] * x); % Discrete update equation
dt = 0.1; % Discrete eq. time step
ts = [0 5]; % From 0 to 5s
x0 = [1; 0]; % Initial continuous state
u0 = 0; % Initial discrete state
[t, x, tu, u] = odehybrid(@ode45, ode, de, dt, ts, x0, u0); % Simulate!
plot(t, x, tu, u, 'r.'); xlabel('Time'); % Plot 'em.
legend('x_1', 'x_2', 'u', 'Location', 'se'); % Label 'em.
Keeping all of the states in a single state vector becomes tedious when managing larger simulations, so odehybrid
can take any number of separate states, hand them separately to the continuous-time and discrete-time functions, and return a log of each separately. The states can be matrices, cell arrays, or structs. We'll extend the above example by adding a disturbance to the ODE and an integrator to the controller to handle the disturbance. Note that we can simply add a new input to the ODE, DE, and initial discrete state cell array.
ode = @(t, x, u, i) [0 1; 2 0] * x ... % Continuous system
+ [0; 1] * u ... % with feedback control
+ [0; 1]; % and with a disturbance
de = @(t, x, u, i) deal(x, ... % No change to cont. state
-[8 4 1] * [x; i], ... % Update input
i + 0.5 * x(1)); % Update integrator
dt = 0.1; % Discrete eq. time step
ts = [0 5]; % From 0 to 5s
x0 = [1; 0]; % Initial continuous state
d0 = {0, 0}; % Initial discrete states
[t, x, td, u, i] = odehybrid(@ode45, ode, de, dt, ts, x0, d0); % Simulate!
plot(t, x, td, [u, i], '.'); xlabel('Time'); % Plot 'em.
legend('x_1', 'x_2', 'u', '\int x_1(t)/2 dt', 'Location', 'se'); % Label.
By using separate inputs for separate states and scoping subsystems as fields of hierarchical structs, one can create an elegant system-of-systems simulation.
Not everything we might want to log is a state, so to log additional values, a logger is included. Just create the log and pass it to odehybrid
; it will take care of passing the logger to the ODEs and DEs. It looks something like this:
% Create the logger.
log = TimeSeriesLogger();
% Add inputs for the logger to the ODE and DE.
ode = @(t, x1, x2, ..., log) ... log.add('alpha', t, a) ...;
de = @(t, x1, x2, ..., log) ... log.add('omega', t, w) ...;
% Provide the logger to odehybrid.
[...] = odehybrid(@ode45, ode, de, dt, ts, x0, d0, [], log);
% Show all of the logged values.
log.plot();
There are many more options for logging. See the documentation for more examples.
The interface mimics the familiar ode45
interface, expanded for the new material.
[t, ... % Output continuous time-steps
x1, x2, ..., % Output continuous states
td, ... % Output discrete time-steps
y1, y2, ... % Output discrete states
te, ... % Output event time steps
x1, x2, ... y1, y2, ... % Output event states
ie, ... % Output event index
] = odehyrbid( ...
@ode45, ... % ODE solver
ode, ... % ODE
{de1, de2, ...}, ... % Discrete update(s)
[dt1, dt2, ...], ... % Discrete update rate(s)
[0 100], ... % Time span
{x1, x2, ...}, ... % Initial continuous states
{y1, y2, ...}, ... % Initial discrete states
option, ... % Options from odeset
log); % Logger
The ODE function should take all of the states as input and return the time-derivative of each of the continuous states. If a log is provided to , it should also take the log as an input.
[x1d, x2d, ...] = f(t, x1, x2, ..., y1, y2, ..., [log]);
The discrete update functions should take all of the states as input and return the states (including continuous states) as outputs. If a log is provided to , it should also take the log as an input.
[x1, x2, ..., y1, y2, ...] = g(t, x1, x2, ..., y1, y2, ..., [log]);
odehybrid
from File Exchange.examples_odehybrid.m
.This is an open-source project moderated by An Uncommon Lab. To contribute, please contact us.