Skip to content
Snippets Groups Projects
SCDclass_algo.m 21.3 KiB
Newer Older
classdef SCDclass_algo
    %SCD algorithm handling object
    %   The class holds all information and 
    %   methods for handling a Simulink
    %   algorithm
Federico Felici's avatar
Federico Felici committed
     
    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
       stdinits            % General initialization scripts
       fpinits             % inits scripts for fixed parameters
       timing              % Timing info structure
       exportedtpsdefaults % default tunable parameters defining functions
       inBus               % name of input bus
       outBus              % name of output bus
       modelslx            % slx model file name
       folder              % folder containing algorithm
        function obj=SCDclass_algo(name)
            % Empty algorithm constructor
            
            obj.modelname=name;
            obj.mdscontainer = SCDclass_mdsobjcontainer;
            obj.taskcontainer = SCDclass_taskcontainer;
            obj.exportedtps = {};
            obj.stdinits  = {};
            obj.fpinits   = {};
            obj.datadictionary = '';           
            obj.refdatadictionaries = {};
            obj.timing.t_start = 0  ;
            obj.timing.t_stop  = 1  ;
            obj.timing.dt      =1e-3;
            % buses
            obj.inBus  = [obj.modelname,'_inBus' ];
            obj.outBus = [obj.modelname,'_outBus'];
            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('*****************************************************\n');                      
        end

        %% Setup

        function setup(obj)
          % setup()
          %
          % calls updatetemplatetp and 
          % buildworkspacetpstruct
          %
          % an algorithm block diagram
          % should pass ctrl-d (instandalone opening)
          % after this call
          obj.buildworkspacetpstruct;
        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 out = getinits(obj)
        %% Data dictionary setter and getter
        function obj = setdatadictionary(obj, datadict)
            obj.datadictionary=datadict;
        end
        
        function out = getdatadictionary(obj)
            out = obj.datadictionary;
        end
        function createdatadictionary(obj,varargin)
          % create data dictionary in path obj.folder/obj.getdatadictionary
          
          dd = obj.getdatadictionary;
          ddpath = fullfile(obj.folder,dd);

          % Close all open models.
          bdclose all
          % Close all open data dictionaries
          Simulink.data.dictionary.closeAll('-discard');
          % 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);
        function obj = setreferenceddatadictionaries(obj,refdd)
          if ischar(refdd) && ~iscell(refdd); refdd={refdd}; end % to cell
          assert(iscell(refdd) && isrow(refdd),'refdd must be a row cell array of data dictionary names')
          obj.refdatadictionaries = refdd;
        end
        
        %% Tunable parameters structures handling functions
        
        function out = getexportedtps(obj)
            out = obj.exportedtps;
        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!');
                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
            % 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);
            DD = 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 DD.exist(tmplname)
                      oldEntry = DD.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)
                        fprintf('   %s: added new %s\n',obj.getname, tmplname);
        
        function obj = buildworkspacetpstruct(obj)
            % this funtion builds a workspace structures containing
            % replicas of all tunable parameters structurea 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
           
            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)
                simstructnamedd=sprintf('%s_tmpl',char(obj.exportedtps(ii)));
                simstructnamews=char(obj.exportedtps(ii));
                simstruct = dd.getEntry(simstructnamedd).getValue;
                assignstr=sprintf('%s=temp;',simstructnamews);
                assignin('base','temp',simstruct);
                evalin('base',assignstr);
            end
            
            evalin('base','clear temp;');            
        end
        
         function addbuses(obj)
           % Add buses to data dictionary from .m file descriptions, if
           % available. Bus names in obj.inBus, obj.outBus
           if ~isempty(which(obj.inBus)) &&  ~isempty(which(obj.outBus))
             dict = Simulink.data.dictionary.open(obj.getdatadictionary);
             designDataObj = getSection(dict, 'Design Data');             
             fprintf('Add buses to data dictionary: %s, %s\n',obj.inBus,obj.outBus);
             eval(obj.inBus); eval(obj.outBus); % eval buses in base workspace
             dosave = false;
             dosave = obj.replaceorcreateddentry(designDataObj,obj.inBus ,evalin('base',obj.inBus  )) || dosave;
             dosave = obj.replaceorcreateddentry(designDataObj,obj.outBus,evalin('base',obj.outBus )) || dosave;
             if dosave, dict.saveChanges; end % Save if necessary
             
             evalin('base',sprintf('clear %s %s',obj.inBus,obj.outBus));
           else
             fprintf('no buses imported in datadictionary - assuming they are already there\n')
           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
       
       %% Generic and fixed parameter inits
       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.\d',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
       
       function printinits(obj)
           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}));
                   fprintf('%s -> %s in workspace %s \n',char(obj.fpinits{ii}{1}),char(obj.fpinits{ii}{2}{1}),char(obj.fpinits{ii}{3}));
       function init(obj)
           obj.callinits; % call generic algorithm init functions 
           obj.setupdd; % setup data dictionary
           
       end
       
       function setupdd(obj)
         % setup data dictionary
         
         % generate/initialize data dictionary
         obj.createdatadictionary;
         
         % link data dictionary to model and enable access to base
         obj.load;
         set_param(obj.getname,'DataDictionary',obj.getdatadictionary,'EnableAccessToBaseWorkspace','on');
         
         % call fixed parameter setup functions
         obj.callfpinits;
         
         % populate with template tunable parameters
         obj.updatetemplatetp;
         
         % add buses
         obj.addbuses;
         
         % add referenced data dictionaries
         obj.addrefdd
         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);
                  if ischar(initfunction)
                    initcmd=sprintf('%s(obj);', initfunction);
                    [value{1:nout}] = eval(initcmd);
                  elseif isa(initfunction,'function_handle')
                      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
                  dosave = false;
                  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
                      dosave = obj.replaceorcreateddentry(dd,target,val) || dosave;
                    elseif strcmp(workspace,'base')
                      % assign in base workspace
                      assignin('base',target,val)
                    end
                  end
                  if dosave, dictionaryObj.saveChanges; end
       
       function addrefdd(obj)
         % add configurations.sldd as referenced data dictionary, plus
         % other optional data dictionaries (e.g. used by lower-level components of an algorithm)
         dosave = false;
         for refdd = ['configurations.sldd',obj.refdatadictionaries]
           dictionaryObj = Simulink.data.dictionary.open(obj.getdatadictionary);
           if ~ismember(refdd{:},dictionaryObj.DataSources)
             fprintf('adding referenced data dictionary %s to %s \n',refdd{:},obj.getdatadictionary)
             refddpath = which(refdd{:});
             assert(~isempty(refddpath),'could not find %s');
             dictionaryObj.addDataSource(refdd{:});
             dosave = true;
           end
           if dosave, dictionaryObj.saveChanges; end
         end
       end
       
       function changed = replaceorcreateddentry(obj,dd,entry,value)
         if dd.exist(entry)
           oldEntry = dd.getEntry(entry);
           if isequal(oldEntry.getValue,value)
             fprintf('%s: keep old value of %s since not changed\n',obj.getname,entry);
             changed = false;
           else
             oldEntry.setValue(value); % replace
             fprintf('%s: replaced value of %s since it changed\n',obj.getname,entry);
             changed = true;
           end
         else
           fprintf('   %s: added new %s\n',obj.getname, entry);
           DD.addEntry(entry,value);
           changed = true;
         end
       end
       %% generic operation methods
       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
         eval(sprintf('%s([],[],[],''term'')',obj.modelname)); % terminate is successful
       function test_harness(obj)
         harnessname = sprintf('%s_harness.slx',obj.getname);
         if ~exist(harnessname,'file')
             warning('no harness %s found, skipping test',harnessname);
             return
         else
             fprintf('running test harness %s\n',harnessname);
             sim(harnessname);
             % 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