Skip to content
Snippets Groups Projects
SCDclass_algo.m 19.6 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            % Standard 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.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)
            out=obj.stdinits;
        end
        %% Data dictionary setter and getter
        function obj = setdatadictionary(obj, datadict)
            obj.datadictionary=datadict;
        end
        
        function out = getdatadictionary(obj)
            out = obj.datadictionary;
        end
        function designDataObj = opendatadictionarydesigndata(obj)
          dict = Simulink.data.dictionary.open(obj.getdatadictionary);
          designDataObj = getSection(dict, 'Design Data');
          
        function dictionaryObj = createdatadictionary(obj,varargin)
         % create and populate data dictionary with parameter,value pairs
         % obj.populatedd('parameter',value);
         
         dd = obj.getdatadictionary;
         % close open data dictionary
         listopendictionaries = Simulink.data.dictionary.getOpenDictionaryPaths;
         if numel(listopendictionaries)>0 && any(contains(listopendictionaries,dd))
           Simulink.data.dictionary.closeAll(dd, '-discard');
         end
         if exist(dd, 'file')
           % delete the existing data dictionary
           delete(dd)
         end
         
         % Create
         ddpath = fullfile(obj.folder,dd);
         dictionaryObj = 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, varargin)
            % 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
                if ~isa(varargin{1}, 'function_handle')
                    error('SCDclass_algo:addtunparamstruct', 'third argument, if present, must be a function handle');
                else
                    obj.exportedtpsdefaults{end+1} = varargin{1};
                end
                
%                 % sets default tunable parameter structure in data dictionary
%                 TP=varargin{1};
%                 if ~isa(TP,'Simulink.Parameter')
%                     assert(isstruct(TP),'default must be a structure');
%                     P = Simulink.Parameter;
%                     P.Value = TP;
%                 else
%                     P = TP;
%                 end
%                 obj.exportedtpsdefaults{end+1} = P;
                obj.exportedtpsdefaults{end+1} = [];
            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
            DD = obj.opendatadictionarydesigndata;
            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)
                        replace = false;
                        oldEntry = DD.getEntry(tmplname);
                        try
                            if isequal(oldEntry.getValue.Value,P.Value)
                                %fprintf('%s: keep old template %s since not changed\n',obj.getname,tmplname);
                                continue;
                            else
                                replace=true;
                            end
                        catch ME
                            replace=true;
                        end
                        if replace
                            DD.deleteEntry(tmplname); fprintf('   %s: deleted previous entry, ', obj.getname);
                            fprintf('added new %s\n',tmplname);
                            DD.addEntry(tmplname,P);
                        end
                    else
                        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
        
        %% 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
       
       %% Fixed inits
       function obj = addfpinitfcn(obj, fcnname, targetstruct,targetworkspace)
          if nargin<3
            targetstruct = ''; 
          end
          if nargin<4
            targetworkspace = 'datadictionary'; % default: data dictionary workspace (assigninGlobal)
          if ~iscell(targetstruct) && ~isempty(targetstruct)
            targetstruct = {targetstruct};
          end
         
           if(~isempty(obj.stdinits))
               for ii=1:numel(obj.stdinits)
                   if ~isempty(targetstruct) && contains(obj.stdinits{ii}{2},targetstruct)
                       warning('SCDclass_algo:addfpinitfcn','A function defining the structure %s has already been added, ignoring.\d',targetstruct)
                       return
           % 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.stdinits{end+1}=temp;
       end
       
       function printinits(obj)
           if(~isempty(obj.stdinits))
               for ii=1:numel(obj.stdinits)
                   fprintf('%s -> %s in workspace %s \n',char(obj.stdinits{ii}{1}),char(obj.stdinits{ii}{2}{1}),char(obj.stdinits{ii}{3}));
       function initdd(obj)
           fprintf('generating data dictionary %s\n',obj.getdatadictionary);
           % generate/initialize data dictionary
           dictionaryObj = obj.createdatadictionary;
           designDataObj = opendatadictionarydesigndata(obj);

           % link data dictionary to model and disable access to base
           obj.load;
           set_param(obj.getname,'DataDictionary',obj.getdatadictionary,'EnableAccessToBaseWorkspace','on');
           % call initialization functions to populate dd with fixed parameters
           % populate with template tunable parameters
           obj.updatetemplatetp;

           % add buses
           if ~isempty(which(obj.inBus)) &&  ~isempty(which(obj.outBus))
             fprintf('Add buses to data dictionary: %s, %s\n',obj.inBus,obj.outBus);
             eval(obj.inBus);  % eval buses in base workspace
             eval(obj.outBus);
             addEntry(designDataObj,obj.inBus ,evalin('base',obj.inBus ));
             addEntry(designDataObj,obj.outBus,evalin('base',obj.outBus));
             evalin('base',sprintf('clear %s %s',obj.inBus,obj.outBus));
           else
             fprintf('no buses imported in datadictionary - assuming they are already there')
           end
           % add configurations.sldd as referenced data dictionary, plus 
           % other optional data dictionaries (e.g. used by lower-level components of an algorithm)
           for refdd = ['configurations.sldd',obj.refdatadictionaries]
              fprintf('adding referenced data dictionary %s to %s \n',refdd{:},obj.getdatadictionary)
              dictionaryObj.addDataSource(refdd{:});
           end
       end
         % call initialization functions that set fixed parameters
          if ~isempty(obj.stdinits)
              for ii=1:numel(obj.stdinits)
                  initfunction = obj.stdinits{ii}{1};
                  targetnames  = obj.stdinits{ii}{2};
                  workspace    = obj.stdinits{ii}{3};
                  
                  nout = numel(targetnames);
                  fprintf('Calling 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 base workspace or datadictionary depending
                  % on target workspace
                  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
                      Simulink.data.assigninGlobal(obj.modelname, target, val);
                    elseif strcmp(workspace,'base')
                      % assign in base workspace
                      assignin('base',target,val)
                    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