Something went wrong on our end
-
Federico Felici authoredFederico Felici authored
SCDclass_algo.m 26.67 KiB
classdef SCDclass_algo
%SCD algorithm handling object
% The class holds all information and
% methods for handling a Simulink
% algorithm
properties (Access = private)
modelname % Name of the model
mdscontainer % Container class for MDS+ interface objects
taskcontainer % Container class for init and term tasks
exportedtps % List of tunable parameters variable to be exported
datadictionary % Name of the used data dictionary
refdatadictionaries % Cell array of referenced data dictionaries
refddparentalgo % Parent algorithm of referenced data dictionaries
stdinits % General initialization scripts
fpinits % inits scripts for fixed parameters
timing % Timing info structure
exportedtpsdefaults % default tunable parameters defining functions
buslist % list of buses and their sources
modelslx % slx model file name
folder % folder containing algorithm
end
methods
function obj=SCDclass_algo(name)
%% Constructor
% Empty algorithm constructor
obj.modelname=name;
obj.mdscontainer = SCDclass_mdsobjcontainer;
obj.taskcontainer = SCDclass_taskcontainer;
obj.exportedtps = {};
obj.exportedtpsdefaults = {};
obj.stdinits = {};
obj.fpinits = {};
obj.datadictionary = '';
obj.refdatadictionaries = {};
obj.timing.t_start = 0 ;
obj.timing.t_stop = 1 ;
obj.timing.dt = 1e-3;
obj.buslist = [];
% Buses
obj.modelslx = [obj.modelname,'.slx'];
% files
% algo path depending on .slx location
assert(~isempty(which(obj.modelslx)),'can''t find slx model %s',obj.modelslx);
obj.folder = fileparts(which(obj.modelslx));
% Standard data dictionary equal to algorithm name,
% overriddable by the setter method
obj=obj.setdatadictionary([name '.sldd']);
end
%% Print infos
function printinfo(obj)
fprintf('*****************************************************\n');
fprintf('* SCD algorithm: ''%s''\n',obj.modelname);
fprintf('*****************************************************\n');
if(~isempty(obj.datadictionary))
fprintf('* Data dictionary:\n %s\n',obj.datadictionary);
else
fprintf('* Data dictionary:\n base workspace\n');
end
if numel(obj.exportedtps)>0
fprintf('* Tunable params structs:\n');
for ii=1:numel(obj.exportedtps)
fprintf(' %s\n',obj.exportedtps{ii});
end
end
fprintf('* Tunable params objects:\n')
obj.printparameters;
fprintf('* Wavegen objects:\n');
obj.printwavegens;
fprintf('* Tasks objects:\n');
obj.printtasks;
fprintf('* Fixed parameters inits:\n');
obj.printinits;
fprintf('* Buses: \n');
obj.printbuslist;
fprintf('*****************************************************\n');
end
function printinits(obj)
fprintf('****** Registered initialization functions ******* \n')
if(~isempty(obj.fpinits))
for ii=1:numel(obj.fpinits)
if isempty(obj.fpinits{ii}{2})
fprintf('%s : init function with no outputs\n',char(obj.fpinits{ii}{1}));
else
fprintf('%s -> %s in workspace %s \n',char(obj.fpinits{ii}{1}),char(obj.fpinits{ii}{2}{1}),char(obj.fpinits{ii}{3}));
end
end
end
end
%% General purpose getters
function out = getname(obj)
out=obj.modelname;
end
function out = getmdscontainer(obj)
out=obj.mdscontainer;
end
function out = gettaskcontainer(obj)
out=obj.taskcontainer;
end
function out = gettiming(obj)
out=obj.timing;
end
function [stdinits, fpinits] = getinits(obj)
stdinits = obj.stdinits;
fpinits = obj.fpinits;
end
function [refdd,refddparent] = getrefdd(obj)
refdd = obj.refdatadictionaries;
refddparent = obj.refddparentalgo;
end
%% Setup functions setter and getter
function obj = addstdinitfcn(obj,fcnhandle)
if ischar(fcnhandle)
fcnhandle = str2func(fcnhandle);
end
assert(isa(fcnhandle,'function_handle'),'stdinit function must be a function handle');
assert(nargout(fcnhandle)<=0,'stdinit functions may not have output arguments')
obj.stdinits{end+1} = fcnhandle;
end
function obj = addfpinitfcn(obj, fcnname, targetstruct,targetworkspace)
if nargin < 4
targetworkspace = 'datadictionary';
end
if ~iscell(targetstruct) && ~isempty(targetstruct)
targetstruct = {targetstruct};
end
for ii=1:numel(obj.fpinits)
for istruct = 1:numel(targetstruct)
mytarget = targetstruct{istruct};
if ~isempty(mytarget) && contains(obj.fpinits{ii}{2},mytarget)
warning('SCDclass_algo:addfpinitfcn',...
'A function defining the structure %s has already been added, ignoring.\n',mytarget)
return
end
end
end
% FF question to CG: why is this a cell and not a struct?
temp=cell(10,1);
temp{1}=fcnname;
temp{2}=targetstruct;
temp{3}=targetworkspace;
obj.fpinits{end+1}=temp;
end
%% Bus setter and getter
function obj = addbus(obj,name,source)
assert(~isequal(name,source),'bus source file and name may not be the same')
% Register a bus from a file to be added to dd
ii = numel(obj.buslist)+1;
obj.buslist(ii).name = name;
obj.buslist(ii).source = source;
end
function list = getbuslist(obj)
list = obj.buslist;
end
function printbuslist(obj)
list = getbuslist(obj);
fprintf('******* Registered bus definitions files for %s: *****\n',obj.modelname)
fprintf('%-25s %-15s %-50s\n','Name','Type','Source')
for ii=1:numel(list)
if isa(list(ii).source,'function_handle')
type = 'function handle';
fcs = functions(list(ii).source);
source = fcs.file;
name = 'DEFINED IN FILE';
else
type = 'file';
name = list(ii).name;
source = which([list(ii).source,'.m']);
end
if isempty(source), source='NOT FOUND'; end
fprintf('%-25s %-15s %-50s\n',name,type,source);
end
fprintf('\n');
end
%% Data dictionary setter and getter
function obj = setdatadictionary(obj, datadict)
obj.datadictionary=datadict;
end
function out = getdatadictionary(obj)
out = obj.datadictionary;
end
function obj = addrefdd(obj,refdd,parentalgo)
% refdd: name of referenced data dictionary
% parentalgo: (optional), SCDclass_algo object of algorithm
% generating this data dicationary.
if nargin<3, parentalgo=''; end
assert(isequal(refdd(end-4:end),'.sldd'),'refdd must end in .sldd')
obj.refddparentalgo{end+1} = parentalgo;
obj.refdatadictionaries{end+1} = refdd;
end
function createdatadictionary(obj,varargin)
% create data dictionary in path obj.folder/obj.getdatadictionary
dd = obj.getdatadictionary;
ddpath = fullfile(obj.folder,dd);
% Close close this model's data dictionary
if contains(Simulink.data.dictionary.getOpenDictionaryPaths,dd)
Simulink.data.dictionary.closeAll(dd);
end
% Create if not existing already
if isempty(which(ddpath))
fprintf('generating data dictionary %s\n',fullfile(obj.folder,obj.getdatadictionary));
Simulink.data.dictionary.create(ddpath);
dd=Simulink.data.dictionary.open(ddpath);
dd.EnableAccessToBaseWorkspace=1;
end
end
%% Data dictionary manipulation manipulations
function addbusestodd(obj)
% Add buses to data dictionary from .m file descriptions
dictionaryObj = Simulink.data.dictionary.open(obj.getdatadictionary);
designDataObj = getSection(dictionaryObj, 'Design Data');
busList = obj.getbuslist;
for ii=1:numel(busList)
mybusSource = busList(ii).source;
busName = busList(ii).name;
if ischar(mybusSource)
% User specified a file that contains a bus definition
% (typically exported from bus editor)
fprintf('Adding bus %s from %s to %s\n',busName,mybusSource,obj.getdatadictionary);
assert(logical(exist(mybusSource,'file')),'%s does not exist',mybusSource)
eval(mybusSource); % eval bus script here
assert(exist(busName,'var')~=0,...
'no variable %s found despite running script %s',busName,which(mybusSource))
obj.replaceorcreateddentry(designDataObj,busName,eval(busName));
elseif isa(mybusSource,'function_handle')
% user specified a function that returns cell arrays of
% bus names and bus objects. busNames is ignored in this case
[names,buses] = mybusSource();
for jj=1:numel(buses)
fprintf('adding bus %25s from function %s to %s',...
names{jj},func2str(mybusSource),obj.getdatadictionary)
obj.replaceorcreateddentry(designDataObj,names{jj},buses{jj});
end
else
error('don''t know how to handle %s',class(mybusSource))
end
end
if dictionaryObj.HasUnsavedChanges, dictionaryObj.saveChanges; end % Save if necessary
end
function addrefddtodd(obj)
% add configurations.sldd as referenced data dictionary, plus
% other optional data dictionaries (e.g. used by lower-level components of an algorithm)
% specified in refdatadictionaries
dictionaryObj = Simulink.data.dictionary.open(obj.getdatadictionary);
refddlist = [obj.refdatadictionaries,'configurations.sldd']; % list of referenced dds
for ii=1:numel(refddlist)
refdd = refddlist{ii};
if ~ismember(refdd,dictionaryObj.DataSources) % does not exist, try to add
fprintf('adding referenced data dictionary %s to %s \n',refdd,obj.getdatadictionary)
refddpath = which(refdd);
if isempty(refddpath) % data dictionary to link does not exist, try to generate it
parentalgo = obj.refddparentalgo{ii};
if isempty(parentalgo)
error('Data dictionary %s was not found but no parent algorithm is specified',refdd{ii})
else
fprintf('Data dictionary %s was not found, running init of parent algorithm %s to create it\n',parentalgo.getname)
% run parent algo init
parentalgo.init;
% check expected dd now exists
refddpath = which(refdd);
assert(~isempty(refddpath),...
'Data dictionary still not found despite running %s init, aborting',parentalgo.getname)
end
end
fprintf('Adding referenced data dictionary %s to %s\n',refdd,obj.getdatadictionary)
dictionaryObj.addDataSource(refdd);
else % already exists
fprintf('Data dictionary %s is already referenced in algo data dictionary, not adding\n',refdd);
end
end
if dictionaryObj.HasUnsavedChanges, dictionaryObj.saveChanges; end
end
function replaceorcreateddentry(obj,designDataObj,entry,value)
if designDataObj.exist(entry)
oldEntry = designDataObj.getEntry(entry);
assert(numel(oldEntry)==1,'multiple entries found for %s',entry)
if isequal(oldEntry.getValue,value)
fprintf('%s: keep old value of %s since not changed\n',obj.getname,entry);
else
oldEntry.setValue(value); % replace
fprintf('%s: replaced value of %s since it changed\n',obj.getname,entry);
end
else
fprintf(' %s: added new %s\n',obj.getname, entry);
designDataObj.addEntry(entry,value);
end
end
%% Tunable parameters structures handling functions
function out = getexportedtps(obj)
out = obj.exportedtps;
end
function obj = addtunparamstruct(obj, structname, default_function)
% obj = addtunparamstruct(structname, varargin)
%
% adds a tunable parameter to the algo object
% inputs:
% structname: name of the tunable param (string)
% varargin{1}: a function handle to get
% its default value, this function takes no arguments
% and must return either a struct or a Simulink.Parameter
%
% example: obj=obj.addtunparamstruct('SCDalgo_doublet_tp',
% @()SCDalgo_doublet_loadtp());
if(~ismember(structname, obj.exportedtps))
obj.exportedtps{end+1}=structname;
else
error('SCDclass_algo:addtunparamsstruct','Tunable parameter struct already present, cannot add!');
end
if nargin==3
assert(isa(default_function, 'function_handle'),'SCDclass_algo:addtunparamstruct', 'third argument, if present, must be a function handle');
obj.exportedtpsdefaults{end+1} = default_function; % add defaults to list
else
obj.exportedtpsdefaults{end+1} = []; % empty, no default assigned for this tp
end
end
function obj = updatetemplatetp(obj)
% obj = updatetemplatetp()
%
% For every configured tunparams structure
% if a default value function is present it is
% updated in the template version of the tun params
% in the algo data dictionary.
% The default value function of a tunparams can be given
% as an optional third argument of addtunparamstruct
% method
dict = Simulink.data.dictionary.open(obj.getdatadictionary);
designDataobj = getSection(dict, 'Design Data');
for ii=1:numel(obj.exportedtps)
if ~isempty(obj.exportedtpsdefaults{ii})
% get current value of parameters from the function
% handle
fprintf(' %s: calling %s\n', obj.getname, char(obj.exportedtpsdefaults{ii}));
TP=obj.exportedtpsdefaults{ii}();
if ~isa(TP,'Simulink.Parameter')
assert(isstruct(TP),'default function must return either a Simulink.Parameter or a structure');
P = Simulink.Parameter;
P.Value = TP;
else
P = TP;
end
P.CoderInfo.StorageClass='ExportedGlobal';
% updates default tunable parameters structure in data dictionary
tmplname = sprintf('%s_tmpl',obj.exportedtps{ii});
if designDataobj.exist(tmplname)
oldEntry = designDataobj.getEntry(tmplname);
if isequal(oldEntry.getValue.Value,P.Value)
fprintf('%s: keep old template %s since not changed\n',obj.getname,tmplname);
continue;
else
% replace
oldEntry.setValue(P);
fprintf('%s: replaced value of template %s since it changed\n',obj.getname,tmplname)
end
else
fprintf(' %s: added new %s\n',obj.getname, tmplname);
designDataobj.addEntry(tmplname,P);
end
end
end
end
function obj = buildworkspacetpstruct(obj)
% obj = buildworkspacetpstruct(obj)
%
% This funtion builds workspace structures containing
% replicas of all tunable parameters structures in the data
% dictionaries, this structure is the one actually used
% for loading simulation data from MDS.
%
% It is better not to use directly data dictionaries structures
% to avoid flooding dds with big sim data sets (and
% consequently the SCD GIT itself
if(isempty(obj.datadictionary))
warning('SCDclass_algo:buildworkspacetpstruct','Methods ignored for this object, data dictionary isn''t configured');
return;
end
dd=SCDconf_getdatadict(obj.datadictionary);
for ii=1:numel(obj.exportedtps)
tp_name = obj.exportedtps{ii};
hasdefault = ~isempty(obj.exportedtpsdefaults);
if hasdefault
tp_tmpl_name=sprintf('%s_tmpl',tp_name);
try
tp_value = dd.getEntry(tp_tmpl_name).getValue;
catch ME
fprintf('error getting %s from dd %s\n',tp_tmpl_name,obj.datadictionary)
rethrow(ME)
end
assignstr=sprintf('%s=temp;',tp_name);
assignin('base','temp',tp_value);
evalin('base',assignstr);
else % no default template defined, define from generating function handle
fprintf('no default defined for %s\n',tp_name)
end
end
evalin('base','clear temp;'); %cleanup
end
function obj = buildworkspacesimdata(obj)
% obj = buildworkspacesimdata(obj)
%
% This function builds a SCDsimdata structure
% containing the necessary timeseries to support
% a standalone simulation of the algorithm
%
% Currently it prepares a timeseries structure
% to be filled with the wavegens configured within
% the algorithm
simdata = struct;
if obj.mdscontainer.numwavegens>0
obj.mdscontainer=obj.mdscontainer.setwavegenbasestruct([obj.getname '_simdata']);
for wgidx=1:obj.mdscontainer.numwavegens
wg=obj.mdscontainer.mdswavegens(wgidx);
eval(['simdata.' wg.gettarget '=timeseries;']);
end
assignin('base',[obj.getname '_simdata'],simdata);
else
warning('SCDclass_algo:buildworkspacesimdata','No wavegens configured for this algo, nothing to do.');
return;
end
end
%% MDS container methods forwarders
function obj = addparameter(obj, param)
obj.mdscontainer=obj.mdscontainer.addparameter(param);
obj.mdscontainer=obj.mdscontainer.bindlastparameter(obj.modelname, obj.datadictionary, obj.exportedtps);
end
function obj = printparameters(obj)
obj.mdscontainer=obj.mdscontainer.printparameters;
end
function obj = actualizeparameters(obj, shot)
obj.mdscontainer=obj.mdscontainer.actualizeparameters(shot);
end
function obj = addwavegen(obj, wavegen)
obj.mdscontainer=obj.mdscontainer.addwavegen(wavegen);
obj.mdscontainer=obj.mdscontainer.bindlastwavegen(obj.modelname, obj.datadictionary, obj.timing);
end
function obj = printwavegens(obj)
obj.mdscontainer=obj.mdscontainer.printwavegens;
end
function obj = actualizewavegens(obj, shot)
obj.mdscontainer=obj.mdscontainer.actualizewavegens(shot);
end
function obj = cleanwavegens(obj)
obj.mdscontainer=obj.mdscontainer.cleanwavegens;
end
%% Task container methods forwarders
function obj = addtask(obj, task)
obj.taskcontainer=obj.taskcontainer.addtask(task);
obj.taskcontainer=obj.taskcontainer.bindlasttask(obj.modelname, obj.datadictionary);
end
function obj = printtasks(obj)
obj.taskcontainer=obj.taskcontainer.printtasks;
end
%% Timing information
function obj = settiming(obj, t_start, dt, t_stop)
obj.timing.t_start=t_start;
obj.timing.dt=dt;
obj.timing.t_stop=t_stop;
end
%% Initializations
function initdd(obj)
% setup data dictionary
% generate data dictionary if it does not exist yet
obj.createdatadictionary;
% link data dictionary to model and enable access to base
obj.load;
% call fixed parameter setup functions
obj.callfpinits;
% populate with template tunable parameters
obj.updatetemplatetp;
% add referenced data dictionaries to obj data dictionary
obj.addrefddtodd
% add buses
obj.addbusestodd;
end
function callinits(obj)
for ii=1:numel(obj.stdinits)
fprintf('calling standard init function %s\n',func2str(obj.stdinits{ii}));
initfunction = obj.stdinits{ii};
initfunction(); % call it
end
end
function callfpinits(obj)
% call initialization functions that set fixed parameters
if ~isempty(obj.fpinits)
for ii=1:numel(obj.fpinits)
initfunction = obj.fpinits{ii}{1};
targetnames = obj.fpinits{ii}{2};
workspace = obj.fpinits{ii}{3};
nout = numel(targetnames);
if ~isempty(targetnames)
fprintf('Calling fixed parameter init function ''%s'', assigning its output to ''%s'' in %s...\n',...
char(initfunction),cell2mat(targetnames),workspace);
end
if ischar(initfunction)
initcmd=sprintf('%s(obj);', initfunction);
[value{1:nout}] = eval(initcmd);
elseif isa(initfunction,'function_handle')
if nargin(initfunction)==1
argins={obj};
elseif nargin(initfunction)==0
argins = {};
else
error('unexpected number of input arguments for function %s',func2str(initfunction));
end
[value{1:nout}] = initfunction(argins{:}); % function has an input argument
else
error('initfunction must be a string or a function handle')
end
% assigns in target datadictionary depending
% on target workspace
fprintf('opening %s\n',obj.getdatadictionary);
dictionaryObj = Simulink.data.dictionary.open(obj.getdatadictionary);
dd = getSection(dictionaryObj, 'Design Data');
for iout = 1:nout
% cycle over number of output arguments of the function
val = value{iout};
target = targetnames{iout};
if strcmp(workspace,'datadictionary')
% assign in associated data dictionary
obj.replaceorcreateddentry(dd,target,val);
elseif strcmp(workspace,'base')
% assign in base workspace
assignin('base',target,val)
end
end
if dictionaryObj.HasUnsavedChanges, dictionaryObj.saveChanges; end
end
end
end
%% generic operation methods
function init(obj)
SCDconf_setConf('SIM');
obj.callinits; % call generic algorithm init functions
obj.initdd; % setup data dictionary
end
function setup(obj)
% setup()
%
% calls updatetemplatetp and
% buildworkspacetpstruct
%
% an algorithm block diagram
% should pass ctrl-d (instandalone opening)
% after this call
obj.updatetemplatetp;
obj.buildworkspacetpstruct;
SCDconf_setConf('sim');
end
function compile(obj)
try
eval(sprintf('%s([],[],[],''compile'')',obj.modelname));
catch ME
try
eval(sprintf('%s([],[],[],''term'')',obj.modelname)); % terminate if necessary
catch
rethrow(ME); % rethrow error so we can see it
end
end
eval(sprintf('%s([],[],[],''term'')',obj.modelname)); % terminate is successful
end
function sim(obj)
sim(obj.modelname);
end
function test_harness(obj)
harnessname = sprintf('%s_harness_run',obj.getname);
if ~exist(harnessname,'file')
warning('no harness %s found, skipping test',harnessname);
return
else
fprintf('running test harness %s\n',harnessname);
run(sprintf('%s(obj)',harnessname)); % input_init + sim + check_result
% add tests to check output result somehow (TBD)
end
end
function open(obj)
open(obj.modelname)
end
function load(obj)
load_system(obj.modelname);
end
end
end