Skip to content
Snippets Groups Projects
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