Write a Simulation
This tutorial shows you how to write a Simulation
.
- Step 1: Create a class
- Step 2: Override run
- Step 3: Add input data to the input map
- Step 4: Use output data to simulate input data
Step 1: Create a class
A Simulation
is simply a MATLAB class that subclasses from symphonyui.core.Simulation
.
Create a new class in your personal Symphony package by navigating to the package in MATLAB's Current Folder, right-clicking on the "+simulations" directory, and selecting New File > Class.
Name the class file "Demo.m" and open it in the MATLAB Editor.
classdef Demo
%DEMO Summary of this class goes here
% Detailed explanation goes here
properties
end
methods
end
end
Remove the comments and properties block and edit the classdef line to subclass from the symphonyui.core.Simulation
class.
classdef Demo < symphonyui.core.Simulation
methods
end
end
You now have an empty Simulation
.
Step 2: Override run
When a simulated DAQ controller needs to "acquire" data, it calls the run()
method of a simulation and passes it the data its "outputting" and the duration of the input data its expecting the simulation to generate. The simulation then decides how to generate input data based on these parameters.
The simulation run()
method is generally called many times over the course of a complete run. The simulated DAQ controller use the simulation to generate only small chunks of data at a time. The duration of these chunks is determined by the simulation time step. The simulation time step is generally 0.5 seconds but may vary.
Override the run()
method in the "Demo" simulation so you can define what it does when a DAQ controller needs to simulate input data for a simulation time step.
classdef Demo < symphonyui.core.Simulation
methods
function inputMap = run(obj, daq, outputMap, timeStep)
end
end
end
The run()
method take three parameters:
- "daq" - The current
DaqController
. - "outputMap" - A map from output stream names (i.e. channels) to output data for the current simulation time step.
- "timeStep" - The duration of the current simulation time step.
Given these parameters, the run()
method must return a single value:
- "inputMap" - A map from input stream names (i.e. channels) to input data for the current simulation time step.
Create an "inputMap" variable by instantiating an empty map.
classdef Demo < symphonyui.core.Simulation
methods
function inputMap = run(obj, daq, outputMap, timeStep)
inputMap = containers.Map();
end
end
end
Add a key to the "inputMap" with the name of each active input streams in the current DaqController
.
inputMap = containers.Map();
inputStreams = daq.getInputStreams();
for i = 1:numel(inputStreams)
inStream = inputStreams{i};
if ~inStream.active
% We don't care to process inactive input streams (i.e. channels without devices).
continue;
end
% Add a key for the active input stream with an empty value.
inputMap(inStream.name) = [];
end
You now have a Simulation
that returns a map with no input data.
classdef Demo < symphonyui.core.Simulation
methods
function inputMap = run(obj, daq, outputMap, timeStep)
inputMap = containers.Map();
inputStreams = daq.getInputStreams();
for i = 1:numel(inputStreams)
inStream = inputStreams{i};
if ~inStream.active
% We don't care to process inactive input streams (i.e. channels without devices).
continue;
end
% Add a key for the active input stream with an empty value.
inputMap(inStream.name) = [];
end
end
end
end
Step 3: Add input data to the input map
If you ran this simulation as is, epochs would never complete because you are not yet producing any input data. You must associate each key in the input map with simulated "acquired" data for the simulation time step.
Simulate a vector of noise data for each input stream by using rand()
for analog input streams and randi()
for digital input streams.
inputMap = containers.Map();
inputStreams = daq.getInputStreams();
for i = 1:numel(inputStreams)
inStream = inputStreams{i};
if ~inStream.active
% We don't care to process inactive input streams (i.e. channels without devices).
continue;
end
% Simulate input data.
rate = inStream.sampleRate;
nsamples = seconds(timeStep) * rate.quantityInBaseUnits;
if strncmp(inStream.name, 'diport', 6)
% Simulate digital noise.
quantities = randi(2^16-1, 1, nsamples);
else
% Simulate analog noise.
quantities = rand(1, nsamples) - 0.5;
end
inputMap(inStream.name) = [];
end
Use the simulated "quantities" vector to instantiate an InputData
object for each key in the map.
inputMap = containers.Map();
inputStreams = daq.getInputStreams();
for i = 1:numel(inputStreams)
inStream = inputStreams{i};
if ~inStream.active
% We don't care to process inactive input streams (i.e. channels without devices).
continue;
end
% Simulate input data.
rate = inStream.sampleRate;
nsamples = seconds(timeStep) * rate.quantityInBaseUnits;
if strncmp(inStream.name, 'diport', 6)
% Simulate digital noise.
quantities = randi(2^16-1, 1, nsamples);
else
% Simulate analog noise.
quantities = rand(1, nsamples) - 0.5;
end
units = inStream.measurementConversionTarget;
inputMap(inStream.name) = symphonyui.core.InputData(quantities, units, rate);
end
You now have a fully functioning Simulation
that simulates noise on each input stream.
classdef Demo < symphonyui.core.Simulation
methods
function inputMap = run(obj, daq, outputMap, timeStep)
inputMap = containers.Map();
inputStreams = daq.getInputStreams();
for i = 1:numel(inputStreams)
inStream = inputStreams{i};
if ~inStream.active
% We don't care to process inactive input streams (i.e. channels without devices).
continue;
end
% Simulate input data.
rate = inStream.sampleRate;
nsamples = seconds(timeStep) * rate.quantityInBaseUnits;
if strncmp(inStream.name, 'diport', 6)
% Simulate digital noise.
quantities = randi(2^16-1, 1, nsamples);
else
% Simulate analog noise.
quantities = rand(1, nsamples) - 0.5;
end
units = inStream.measurementConversionTarget;
inputMap(inStream.name) = symphonyui.core.InputData(quantities, units, rate);
end
end
end
end
Step 4: Use output data to simulate input data
The "outputMap" parameter passed into the simulation function allows you to access the output data for each active output stream in the current simulation time step. You can use this output data to determine how you want your simulation function to simulate input data.
Add an additional if
statement to the "Simulate input data" if
block to determine if the current stream in the loop is the analog input 0 stream (i.e. if it has the stream name "ai0"). If so, get a vector of the quantities for the output data on the analog output 0 stream from the "outputMap" and use it (with some added noise) as input data for the analog input 0 stream.
inputMap = containers.Map();
inputStreams = daq.getInputStreams();
for i = 1:numel(inputStreams)
inStream = inputStreams{i};
if ~inStream.active
% We don't care to process inactive input streams (i.e. channels without devices).
continue;
end
% Simulate input data.
rate = inStream.sampleRate;
nsamples = seconds(timeStep) * rate.quantityInBaseUnits;
if strcmp(inStream.name, 'ai0')
% Simulate the output signal + noise.
outData = outputMap('ao0');
[outQuantities, outUnits] = outData.getData();
quantities = outQuantities + rand(1, nsamples) - 0.5;
elseif strncmp(inStream.name, 'diport', 6)
% Simulate digital noise.
quantities = randi(2^16-1, 1, nsamples);
else
% Simulate analog noise.
quantities = rand(1, nsamples) - 0.5;
end
units = inStream.measurementConversionTarget;
inputMap(inStream.name) = symphonyui.core.InputData(quantities, units, rate);
end
You now have a fully functioning Simulation
that simulates a loopback of the analog output 0 signal on the analog input 0 stream and noise on all other input streams.
classdef Demo < symphonyui.core.Simulation
methods
function inputMap = run(obj, daq, outputMap, timeStep)
inputMap = containers.Map();
inputStreams = daq.getInputStreams();
for i = 1:numel(inputStreams)
inStream = inputStreams{i};
if ~inStream.active
% We don't care to process inactive input streams (i.e. channels without devices).
continue;
end
% Simulate input data.
rate = inStream.sampleRate;
nsamples = seconds(timeStep) * rate.quantityInBaseUnits;
if strcmp(inStream.name, 'ai0')
% Simulate the output signal + noise.
outData = outputMap('ao0');
[outQuantities, outUnits] = outData.getData();
quantities = outQuantities + rand(1, nsamples) - 0.5;
elseif strncmp(inStream.name, 'diport', 6)
% Simulate digital noise.
quantities = randi(2^16-1, 1, nsamples);
else
% Simulate analog noise.
quantities = rand(1, nsamples) - 0.5;
end
units = inStream.measurementConversionTarget;
inputMap(inStream.name) = symphonyui.core.InputData(quantities, units, rate);
end
end
end
end
To use a Simulation
, refer to the "Set the Simulation for a DAQ Controller" tutorial.