Realize Batch Simulation and Use simulationEnsembleDatastore
to Organize Simulation Results in MATLAB Simulink
建立模型
创建模型 model.slx,该模型含有两个信号源,分别是 Sine Wave 和 Cosine Wave
它们的幅值和角频率分别由变量 A1
、f1
和 A2
、f2
控制,默认值在 PreLoadFcn
中进行设置。它们的波形经过 Plus 模块相加后,输出到 Sum 示波器中进行显示。
设置 Signal Logging
设置 Signal Logging ,设置方式见博客:【MATLAB Simulink】设置 Signal Logging
将正弦信号、余弦信号以及两者的合成信号都记录下来,并且分别命名为 SineWave
,CosineWave
和 CompositeWave
。
批量运行仿真:generateSimulationEnsemble
函数
使用脚本给变量 A1
、f1
和 A2
、f2
循环赋值,并使用 generateSimulationEnsemble
批量运行仿真:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
mdl = 'model';
open_system(mdl)
A1s = 3 : 0.5 : 5;
f1s = pi/4 : pi/12 : pi/2;
A2s = 7 : 0.5 : 9;
f2s = pi/3 : pi/6 : pi;
[A1Values, f1Values, A2Values, f2Values] = ndgrid(A1s, f1s, A2s, f2s);
for ct = numel(A1Values):-1:1
in = Simulink.SimulationInput(mdl);
in = in.setVariable('A1', A1Values(ct));
in = in.setVariable('f1', f1Values(ct));
in = in.setVariable('A2', A2Values(ct));
in = in.setVariable('f2', f2Values(ct));
gridSimulationInput(ct) = in;
end
if ~exist(fullfile(pwd,'Data'),'dir')
mkdir(fullfile(pwd,'Data')) % Create directory to store results
end
runAll = true;
if runAll
[ok,e] = generateSimulationEnsemble(gridSimulationInput, fullfile(pwd,'Data'),'UseParallel', false);
else
[ok,e] = generateSimulationEnsemble(gridSimulationInput(1:10), fullfile(pwd,'Data'));
end
仿真运行中
.mat 仿真信息文件概览
运行完毕后,可以在当前文件夹中看到 Data 子文件夹,其中的 .mat 文件保存了每次仿真的运行信息
每个 .mat 文件都包含了4个变量,分别是 logsout
, PMSignalLogName
,SimulationInput
, SimulationMetadata
:
PMSignalLogName
变量和 logsout
变量
变量 logsout
,保存了所记录信号的信息,并且该变量的名称保存在 PMSignalLogName
中
SimulationInput
变量
变量 SimulationInput
保存了仿真输入的信息
其中,SimulationInput.Variables
中保存着变量 A1
、f1
和 A2
、f2
的值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
>> SimulationInput.Variables(1)
ans =
Variable with properties:
Name: 'A1'
Value: 3
Workspace: 'global-workspace'
Description: ""
>> SimulationInput.Variables(2)
ans =
Variable with properties:
Name: 'f1'
Value: 0.7854
Workspace: 'global-workspace'
Description: ""
>> SimulationInput.Variables(3)
ans =
Variable with properties:
Name: 'A2'
Value: 7
Workspace: 'global-workspace'
Description: ""
>> SimulationInput.Variables(4)
ans =
Variable with properties:
Name: 'f2'
Value: 1.0472
Workspace: 'global-workspace'
Description: ""
SimulationMetadata
变量
变量 SimulationMetadata
中保存着仿真的其他信息
创建并读取 Simulation ensemble datastore
创建 :simulationEnsembleDatastore()
函数
函数 simulationEnsembleDatasotre
提供了一种批量管理、操作上述 .mat 文件的方式。
首先,创建 Simulation ensemble datastore:
1
ens = simulationEnsembleDatastore(fullfile(pwd,'Data'));
之后查看 ens
中保存的变量
1
2
3
4
5
6
7
8
9
10
>> ens.DataVariables
ans =
5×1 string array
"CompositeWave"
"CosineWave"
"SimulationInput"
"SimulationMetadata"
"SineWave"
可以看到,变量 SineWave
,CosineWave
,和 CompositeWave
从变量 logsout
中提取出来了,这一操作可能是 MATLAB 根据变量 PMSignalLogName
进行操作的。
读取:read()
函数
之后查看指定变量,并读取数据:
1
2
3
4
5
6
7
8
9
10
ens.SelectedVariables = ["SineWave", "CosineWave", "CompositeWave"];
read(ens)
%--------------------------------------
1×3 table
SineWave CosineWave CompositeWave
__________________ __________________ __________________
{5001×1 timetable} {5001×1 timetable} {5001×1 timetable}
⚠⚠⚠注意⚠⚠⚠
代码 ens.SelectedVariables = ["SineWave", "CosineWave", "CompositeWave"];
中不能使用单引号,否则会报错,无法运行。
重置:reset()
函数
上述 ens = simulationEnsembleDatastore()
的操作并没有把所有的数据都放入到一个表中(这么做会占取大量的电脑资源),它只是创建了一个索引表:
其中,ens.Files
保存了 .mat 文件的文件名(BTW,文件并不是按照1~500的顺序排列,而是按照拼写排序,但这在大多数情况下并不影响操作)
变量 ens.LastMemberRead
中保存了最后一次读取的文件,相当于是一个 cursor。
当 ens
刚刚创建时,ens.LastMemberRead
是一个空字符串:
1
2
3
4
5
>> ens.LastMemberRead
ans =
0×0 empty string array
之后,每使用一次 read(ens)
操作,ens.LastMemberRead
都会变化,按照 ens.Files
向下读取。
这样的程序设置有一定的便利性,但是有时候会产生一些预期之外的结果。因此,在一些场景下,比如想要使用 for
循环对每一个文件都进行相同的操作,而为了保证遍历到每一个 .mat 文件,需要在循环前进行 reset(ens)
。
⚠注意: reset()
函数并不会重置 ens.SelectedVariables
。
借助 ens 向 .mat 文件写入数据:writeToLastMemberRead
函数
写入单个文件
现在,我们的 .mat 文件的内容是这样的:
ens.DataVariables
有这些:
1
2
3
4
5
6
7
8
9
10
>> ens.DataVariables
ans =
5×1 string array
"CompositeWave"
"CosineWave"
"SimulationInput"
"SimulationMetadata"
"SineWave"
我们想要进行下面几个简单的操作
(1)在 .mat 文件中把变量 SineWave
,CosineWave
和 CompositeWave
从 logsout
提取出来;
(2)把每个仿真文件中的 CompositeWave
的最大值提取出来,将其保存在变量 Maximum
中,并将其返回到 .mat 文件中;
(3)将仿真输入参数 A1
、f1
和 A2
、f2
从变量 SimulationInput
中提取出来;
提取仿真输出信号
1
2
3
4
5
6
7
8
9
10
11
12
%% Extract three logged signal
reset(ens)
ens.SelectedVariables = ["SineWave", "CosineWave", "CompositeWave"];
data = read(ens);
sinewave = data.SineWave{1};
cosinewave = data.CosineWave{1};
compositewave = data.CompositeWave{1};
% Add values
addData = table({sinewave}, {cosinewave}, {compositewave}, ...
'VariableNames',{'SineWave', 'CosineWave', 'CompositeWave'});
writeToLastMemberRead(ens, addData);
此时,.mat 文件中保存的数据:
提取 CompositeWave
最大值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%% Extract maximum value of the composite signal
reset(ens)
% Extract the maximum value
ens.SelectedVariables = "CompositeWave";
data = read(ens);
compositewave = data.CompositeWave{1};
Maximum = max(compositewave.Data);
% Add new variable
ens.DataVariables = [ens.DataVariables; "Maximum"];
% Add correspongding value
addData = table(Maximum, 'VariableNames', "Maximum");
writeToLastMemberRead(ens, addData);
此时,.mat 文件中保存的数据:
提取仿真输入参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
%% Extract input parameters
reset(ens)
ens.SelectedVariables = "SimulationInput";
data = read(ens);
vars = data.SimulationInput{1}.Variables;
idx = strcmp({vars.Name},'A1');
A1 = vars(idx).Value;
idx = strcmp({vars.Name},'f1');
f1 = vars(idx).Value;
idx = strcmp({vars.Name},'A2');
A2 = vars(idx).Value;
idx = strcmp({vars.Name},'f2');
f2 = vars(idx).Value;
% Add new variable
ens.DataVariables = [ens.DataVariables; "A1"; "f1"; "A2"; "f2"];
% Add corresponding value
addData = table(A1, f1, A2, f2, 'VariableNames', {'A1', 'f1', 'A2', 'f2'});
writeToLastMemberRead(ens, addData);
此时,.mat 文件中保存的数据:
批量写入
上述过程只是对第1个 .mat 文件进行操作:
不涉及其他文件:
可以将上述过程封装成 preparedata
函数,并写作循环形式,对每一个 .mat 文件进行操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
clc, clear
% Create ensemble
ens = simulationEnsembleDatastore(fullfile(pwd,'Data'));
ens.SelectedVariables = ["SineWave", "CosineWave", "CompositeWave", "SimulationInput"];
% Add new variable
ens.DataVariables = [ens.DataVariables; "Maximum"; "A1"; "f1"; "A2"; "f2"];
reset(ens);
while hasdata(ens)
data = read(ens);
addData = prepareData(data);
writeToLastMemberRead(ens, addData)
end
function addData = prepareData(data)
sinewave = data.SineWave{1};
cosinewave = data.CosineWave{1};
compositewave = data.CompositeWave{1};
Maximum = max(compositewave.Data);
vars = data.SimulationInput{1}.Variables;
idx = strcmp({vars.Name},'A1');
A1 = vars(idx).Value;
idx = strcmp({vars.Name},'f1');
f1 = vars(idx).Value;
idx = strcmp({vars.Name},'A2');
A2 = vars(idx).Value;
idx = strcmp({vars.Name},'f2');
f2 = vars(idx).Value;
% The table of additional values
addData = table({sinewave}, {cosinewave}, {compositewave}, Maximum, A1, f1, A2, f2, ...
'VariableNames',{'SineWave', 'CosineWave', 'CompositeWave', 'Maximum', 'A1', 'f1', 'A2', 'f2' });
end
运行即可。
总结
总结上述写入操作的步骤:
- 根据已有的多个 .mat 文件,使用
simulationEnsembleDatastore()
创建ens
; - 设置
ens.SelectedVariables
,选取后续操作需要用到的 已有 变量; - 使用
data=read(ens)
读取 .mat 文件的内容; - 对读取到的数据进行处理和操作,包括类似 信号预处理、特征提取 等操作;
- 使用类似
ens.DataVariables = [ens.DataVariables; "Maximum"; "A1"];
为ens
添加 新增 变量; - 创建包含新增变量及其值的 table,
addData = table({XX},{XX},XX,XX,'VariableNames', {'XX','XX','XX','XX'});
; - 将
addData
写入 .mat 文件:writeToLastMemberRead(ens, addData)
删除变量
问题
现在,.mat 文件中保存的变量:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>> whos('-file', 'model_log_1.mat')
Name Size Bytes Class
A1 1x1 8 double
A2 1x1 8 double
CompositeWave - 81303 timetable
CosineWave - 81303 timetable
Maximum 1x1 8 double
PMSignalLogName 1x7 14 char
SimulationInput 1x1 1061 Simulink.SimulationInput
SimulationMetadata 1x1 6632 Simulink.SimulationMetadata
SineWave - 81303 timetable
f1 1x1 8 double
f2 1x1 8 double
logsout 1x1 244801 Simulink.SimulationData.Dataset
假如我们想要删除每一个 .mat 文件中的 A1
、f1
、A2
、f2
应该怎么做呢?
目前,我还没有找到很好的官方办法,但是大概知道问题出现在哪里。
基于数据文件创建 ensemble 的一般化方法:ens = fileEnsembleDatastore
上述过程中创建 ensemble 时都使用的是 ens = simulationEnsembleDatastore()
,这个函数是处理Simulink 仿真中产生的数据构成的 .mat 文件。由于处理的是固定类型的数据,它的灵活性就较低,不支持用户自定义读取函数和写入函数。
如果我们想使用类似的工作流管理其他类型的 .mat 文件,则需要使用 fensemble = fileEnsembleDatastore()
创建 ensemble,此时我们可以也必须自定义 fensemble.ReadFcn
函数和 fensemble.WriteToMemberFcn
函数,例如:
1
2
3
% set custom ReadFcn & WriteToMemberFcn function
fensemble.ReadFcn = @readData;
fensemble.WriteToMemberFcn = @writeNewData;
而官方提供的一个 writeNewData
函数为:
1
2
3
function writeNewData(filename,data)
save(filename, '-append', '-struct', 'data');
end
这里定义写入行为和上面的 simulationEnsembleDatastore
所对应的写入行为是一致的,flag 都是-append
,但是 fileEnsembleDatastore
允许我们对此进行修改。
save 函数的 -append
flag
我们可以对比一下在 save 函数中是否使用 -append
flag 之间的区别:
(1)使用 -append
flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
% create 'test.mat'
p = rand(1,10);
q = ones(10);
save('test.mat','p','q')
% List variables in 'test.mat'
whos('-file','test.mat')
% append 'a' in 'test.mat'
a = 50;
save('test.mat','a','-append')
% List varibles in new 'test.mat'
whos('-file','test.mat')
结果
1
2
3
4
5
6
7
8
9
10
11
>> script5
Name Size Bytes Class Attributes
p 1x10 80 double
q 10x10 800 double
Name Size Bytes Class Attributes
a 1x1 8 double
p 1x10 80 double
q 10x10 800 double
(2)不使用 -append
flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
% create 'test.mat'
p = rand(1,10);
q = ones(10);
save('test.mat','p','q')
% List variables in 'test.mat'
whos('-file','test.mat')
% append 'a' in 'test.mat'
a = 50;
save('test.mat','a')
% List varibles in new 'test.mat'
whos('-file','test.mat')
结果:
1
2
3
4
5
6
7
8
9
>> script6
Name Size Bytes Class Attributes
p 1x10 80 double
q 10x10 800 double
Name Size Bytes Class Attributes
a 1x1 8 double
一种实现“删除变量”操作的方式
根据上述的分析,可以得到一种比较麻烦的实现删除变量操作的方式,即使用 fileEnsembleDatastore
为现有的 .mat 文件创建 ensemble,并自定义 fensemble.WriteToMemberFcn
函数,使 save
的行为只是简单的保存,而不是 append。之后,读取每一个 .mat 文件的所有变量构成一个 table,然后使用 removevars
函数删除不必要的列,之后将删除后的 table 写入到原来的 .mat 文件中,实现删除操作。
注意,我们的重点是自定义fensemble.WriteToMemberFcn
函数,但是函数 fensemble.ReadFcn
也需要预先定义,才能实现 read
操作。
假如,如果我们想要实现删除掉每一个 .mat 文件中的变量 A1
、f1
、A2
、f2
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fens = fileEnsembleDatastore(fullfile(pwd,'Data'),'.mat');
fens.ReadFcn = @readVars;
fens.WriteToMemberFcn = @writeNewData;
fens.DataVariables = ["SineWave"; "CosineWave"; "CompositeWave"; "PMSignalLogName";
"logsout";"SimulationInput"; "SimulationMetadata"; "A1"; "f1"; "A2"; "f2"; "Maximum"];
fens.SelectedVariables = fens.DataVariables;
data = read(fens);
data = removevars(data, {'A1', 'f1', 'A2', 'f2'});
writeToLastMemberRead(fens, data)
function data = readVars(filename,variables)
data = table();
mfile = matfile(filename); % Allows partial loading
for ct=1:numel(variables)
val = mfile.(variables{ct});
if numel(val) > 1
val = {val};
end
data.(variables{ct}) = val;
end
end
function writeNewData(filename,data)
save(filename, '-struct', 'data');
end
此时:
1
2
3
4
5
6
7
8
9
10
11
>> whos('-file', 'model_log_1.mat')
Name Size Bytes Class
CompositeWave - 81303 timetable
CosineWave - 81303 timetable
Maximum 1x1 8 double
PMSignalLogName 1x7 14 char
SimulationInput 1x1 1061 Simulink.SimulationInput
SimulationMetadata 1x1 6632 Simulink.SimulationMetadata
SineWave - 81303 timetable
logsout 1x1 244801 Simulink.SimulationData.Dataset
虽然有些僵硬,但是 bingo~
之后,对所有 .mat 进行批量删除操作,但是和批量写入不同的是,重复的写入并不会报错,但是重复的删除会出现报错,比如刚才我们对第一个 .mat 文件进行了删除操作,这些变量已经没有了,再次删除(甚至是再次读取都会报错,因为我们设置的 fens.DataVariables
中包含 A1
那几个变量)就会报错,因此在整个流程之前先简单设置一个 fens.SelectedVariables
,先 read
一下,跳过第一个文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
fens = fileEnsembleDatastore(fullfile(pwd,'Data'),'.mat');
fens.ReadFcn = @readVars;
fens.WriteToMemberFcn = @writeNewData;
fens.DataVariables = ["SineWave"; "CosineWave"; "CompositeWave"; "PMSignalLogName";
"logsout";"SimulationInput"; "SimulationMetadata"; "A1"; "f1"; "A2"; "f2"; "Maximum"];
% Skip the first .mat file
reset(fens);
fens.SelectedVariables = "SineWave";
data = read(fens);
% Operate on the remaining files
fens.SelectedVariables = fens.DataVariables;
while hasdata(fens)
data = read(fens);
data = removevars(data, {'A1', 'f1', 'A2', 'f2'});
writeToLastMemberRead(fens, data)
end
function data = readVars(filename,variables)
data = table();
mfile = matfile(filename); % Allows partial loading
for ct=1:numel(variables)
val = mfile.(variables{ct});
if numel(val) > 1
val = {val};
end
data.(variables{ct}) = val;
end
end
function writeNewData(filename,data)
save(filename, '-struct', 'data');
end
或者比较简单的方式,使用 subset()
函数将剩余的 .mat 文件作为 fens 的子集,之后进行删除操作。
Bingo~
总的来说,“删除”操作比“写入”操作更加麻烦,因为就像上面所遇到的情况,重复删除是会报错的,必须重新定义fens.SelectedVariables
。所以,如果不是 .mat 文件中的变量特别多或者特别复杂,不用删除掉那些变量,只需要设置好 ens.SelectedVariables
,用什么取什么即可。
参考
[1] simulationEnsembleDatastore - MathWorks.