Home > functions > internal > dsPropagateFunctions.m

dsPropagateFunctions

PURPOSE ^

PROPAGATEFUNCTIONS - eliminate internal function calls from model ODEs, ICs, monitors, and conditionals.

SYNOPSIS ^

function model = dsPropagateFunctions(model, varargin)

DESCRIPTION ^

PROPAGATEFUNCTIONS - eliminate internal function calls from model ODEs, ICs, monitors, and conditionals.

 Usage:
   model = SubstituteFunctions(model)

 Input: DynaSim model structure

 Output: DynaSim model structure without internal function calls

 See also: dsSimulate, dsGenerateModel, dsPropagateNamespaces

 Author: Jason Sherfey, PhD <jssherfey@gmail.com>
 Copyright (C) 2016 Jason Sherfey, Boston University, USA

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SUBFUNCTIONS ^

SOURCE CODE ^

0001 function model = dsPropagateFunctions(model, varargin)
0002 %PROPAGATEFUNCTIONS - eliminate internal function calls from model ODEs, ICs, monitors, and conditionals.
0003 %
0004 % Usage:
0005 %   model = SubstituteFunctions(model)
0006 %
0007 % Input: DynaSim model structure
0008 %
0009 % Output: DynaSim model structure without internal function calls
0010 %
0011 % See also: dsSimulate, dsGenerateModel, dsPropagateNamespaces
0012 %
0013 % Author: Jason Sherfey, PhD <jssherfey@gmail.com>
0014 % Copyright (C) 2016 Jason Sherfey, Boston University, USA
0015 
0016 %% localfn output
0017 if ~nargin
0018   model = localfunctions; % output var name specific to this fn
0019   return
0020 end
0021 
0022 %% auto_gen_test_data_flag argin
0023 options = dsCheckOptions(varargin,{'auto_gen_test_data_flag',0,{0,1}},false);
0024 if options.auto_gen_test_data_flag
0025   varargs = varargin;
0026   varargs{find(strcmp(varargs, 'auto_gen_test_data_flag'))+1} = 0;
0027   varargs(end+1:end+2) = {'unit_test_flag',1};
0028   argin = [{model}, varargs]; % specific to this function
0029 end
0030 
0031 % Check inputs
0032 model=dsCheckModel(model, varargin{:});
0033 if ~isstruct(model.functions)
0034   % nothing to do
0035   return;
0036 end
0037 
0038 %% 1.0 Substitute functions into functions
0039 % note: sequence of function-substitutions-into-functions forms a directed
0040 % acyclic graph (DAG); eg, (3,4)->2->(1,5). may be able to use that fact to
0041 % determine the optimal finite sequence of substitutions without need for a
0042 % while statement. try improving in the future...
0043 
0044 % approach for now: loop through function list, substitute functions into
0045 % functions; repeat until no functions have additional substitutions to do.
0046 keep_going=1;
0047 while keep_going
0048   keep_going=0;
0049   update_these=fieldnames(model.functions);
0050   expressions=struct2cell(model.functions);
0051   
0052   % loop over target functions from which to eliminate internal function calls
0053   for i=1:length(expressions)
0054     functions=model.functions; % update functions on each iteration
0055     [expressions{i},keep_going]=insert_functions(expressions{i},functions, varargin{:});
0056     model.functions.(update_these{i})=expressions{i};
0057   end
0058 end
0059 
0060 % substitute these updated functions into everything else:
0061 functions=model.functions;
0062 
0063 %% 2.0 Substitute functions into ODEs, ICs, and monitors (sub-structures)
0064 target_types={'monitors','ODEs','ICs'};
0065 % loop over types of model data
0066 for type_index=1:length(target_types)
0067   type=target_types{type_index};
0068   % info for this type
0069   s=model.(type);
0070   if isstruct(s)
0071     update_these=fieldnames(s);
0072     expressions=struct2cell(s);
0073     
0074     % loop over target expressions from which to eliminate internal function calls
0075     for i=1:length(expressions)
0076       if isempty(expressions{i})
0077         continue;
0078       end
0079       
0080       % update expressions of this type
0081       expressions{i}=insert_functions(expressions{i},functions, varargin{:});
0082     end
0083     
0084     % update model with expressions that do not require internal function calls
0085     model.(type)=cell2struct(expressions,update_these,1);
0086   end
0087 end
0088 
0089 %% 3.0 Substitute functions into conditionals (structure array)
0090 if ~isempty(model.conditionals)
0091   target_types={'condition','action','else'};
0092   for type_index=1:length(target_types)
0093     type=target_types{type_index};
0094     expressions={model.conditionals.(type)};
0095     
0096     % loop over conditional expressions from which to eliminate internal function calls
0097     for i=1:length(expressions)
0098       if isempty(expressions{i})
0099         continue;
0100       end
0101       
0102       % update expressions
0103       expressions{i}=insert_functions(expressions{i},functions, varargin{:});
0104     end
0105     [model.conditionals(1:length(model.conditionals)).(type)]=deal(expressions{:});
0106   end
0107 end
0108 
0109 %% auto_gen_test_data_flag argout
0110 if options.auto_gen_test_data_flag
0111   argout = {model}; % specific to this function
0112   
0113   dsUnitSaveAutoGenTestData(argin, argout);
0114 end
0115 
0116 end % main fn
0117 
0118 %% SUBFUNCTIONS
0119 function [expression,functions_were_found] = insert_functions(expression,functions, varargin)
0120 
0121   %% auto_gen_test_data_flag argin
0122   options = dsCheckOptions(varargin,{'auto_gen_test_data_flag',0,{0,1}},false);
0123   if options.auto_gen_test_data_flag
0124     varargs = varargin;
0125     varargs{find(strcmp(varargs, 'auto_gen_test_data_flag'))+1} = 0;
0126     varargs(end+1:end+2) = {'unit_test_flag',1};
0127     argin = [{expression}, {functions}, varargs]; % specific to this function
0128   end
0129 
0130   functions_were_found=0;
0131   % get list of functions called by this target function
0132   words=unique(regexp(expression,'[a-zA-Z]+\w*','match'));
0133   found_functions=words(ismember(words,fieldnames(functions)));
0134   if ~isempty(found_functions)
0135     functions_were_found=1;
0136     % substitute those found into this target functions
0137     for ff=1:length(found_functions)
0138       % name of found function
0139       found_function=found_functions{ff};
0140 
0141       % found expression to replace found function name in target
0142       found_expression=functions.(found_function);
0143 
0144       % variable names used in the original found function definition
0145       orig_var_list=regexp(found_expression,'^@\(([^\)]+)\)','tokens','once');
0146       orig_vars=regexp(orig_var_list{1},',','split'); % variables used in original function definition
0147 
0148       % variable names passed from the target function to the function found in it
0149       % get arguments to function call, support function arguments
0150       %       new_var_list=regexp(expression,[found_function '\(*\(([^\)\(]+)\)'],'tokens','once');
0151       index=regexp(expression,[found_function '\('],'once');
0152       substr=expression(index:end); % string starting with first function call
0153       lb=find(substr=='('); % indices to open parentheses
0154       rb=find(substr==')'); % indices to close parentheses
0155       ix=ones(size(lb)); % binary vector indicating open parentheses that have not been closed
0156 
0157       for i=1:length(rb)
0158         pos=find(lb<rb(i)&ix==1,1,'last'); % last open parentheses before this closing parenthesis
0159         if pos==1 % this closing parenthesis closes the function call
0160           R=rb(i);
0161           break;
0162         else % this closing parenthesis closes a grouped expression within the arguments of the function call
0163           ix(pos)=0; % this open parenthesis has been closed
0164         end
0165       end
0166 
0167       % add escape character to regexp special characters
0168       new_var_list{1}=regexprep(substr(lb(1)+1:R-1),'([\(\)\+\*\.\^])','\\$1');
0169 
0170       % split variables on comma
0171       new_vars=regexp(new_var_list{1},',','split');
0172 
0173       % found expression without the input variable list
0174       found_expression=regexp(found_expression,'^@\([^\)]+\)(.+)','tokens','once');
0175       found_expression=found_expression{1};
0176 
0177       if length(orig_vars)~=length(new_vars)
0178         error('failed to match variables for function %s',found_function);
0179       end
0180 
0181       % prepare found expression with variable names from the target function
0182       if ~isequal(orig_vars,new_vars)
0183         for v=1:length(orig_vars)
0184           found_expression=dsStrrep(found_expression,orig_vars{v},['(' new_vars{v} ')'], '', '', varargin{:});
0185         end
0186       end
0187 
0188       % string to replace in the target function
0189       oldstr=[found_function '\(' new_var_list{1} '\)'];
0190 
0191       % string to insert in the target function
0192       newstr=sprintf('(%s)',found_expression);
0193 
0194       % update the target function
0195       expression=dsStrrep(expression,oldstr,newstr,'(',')', varargin{:});
0196     end
0197   end
0198 
0199   %% auto_gen_test_data_flag argout
0200   if options.auto_gen_test_data_flag
0201     argout = {expression, functions_were_found}; % specific to this function
0202 
0203     dsUnitSaveAutoGenTestDataLocalFn(argin, argout); % localfn
0204   end
0205 
0206 end

Generated on Tue 12-Dec-2017 11:32:10 by m2html © 2005