CLASSIFYEQUATION - use regular expressions to classify model expressions in STRING Usage: CLASS=dsClassifyEquation(STRING,DELIMITER) Inputs: - STRING - DELIMITER (optional character, default=';'): delimit expressions in STRING Output: - CLASS (string or cell array of strings for each delimited expression): class: format: parameter name=value fixed_variable name=expression/data function name(inputs)=expression ODE dx/dt or x' = expression IC x(0)=values conditional if(condition)(action) or if(condition)(action)else(action) monitor monitor * (previously: monitor name=expression) linker {'+=','-=','*=','/='} ('=>'for backwards compatibility) {'>-','>+','>*',or '>\'} comment % or # Notes: - Output "class" will be a cell array of strings if STRING contains multiple expressions; otherwise it will be a string. - This function is designed to be an internal helper function called by user-level functions in DynaSim. Examples: class=dsClassifyEquation('dx/dt=3*a*x') classes=dsClassifyEquation('dx/dt=3*a*x; x(0)=0') classes=dsClassifyEquation('dx/dt=3*a*x, x(0)=0',',') classes=dsClassifyEquation('a=2; b=2*a; f(x)=b; dx/dt=f(x); x(0)=0; if(x>1)(x=0); current=>f(x); monitor f(x); % comments') classes=dsClassifyEquation('model.eqns'); See also: dsParseModelEquations Author: Jason Sherfey, PhD <jssherfey@gmail.com> Copyright (C) 2016 Jason Sherfey, Boston University, USA
0001 function classes = dsClassifyEquation(string,delimiter, varargin) 0002 %CLASSIFYEQUATION - use regular expressions to classify model expressions in STRING 0003 % 0004 % Usage: 0005 % CLASS=dsClassifyEquation(STRING,DELIMITER) 0006 % 0007 % Inputs: 0008 % - STRING 0009 % - DELIMITER (optional character, default=';'): delimit expressions in STRING 0010 % 0011 % Output: 0012 % - CLASS (string or cell array of strings for each delimited expression): 0013 % class: format: 0014 % parameter name=value 0015 % fixed_variable name=expression/data 0016 % function name(inputs)=expression 0017 % ODE dx/dt or x' = expression 0018 % IC x(0)=values 0019 % conditional if(condition)(action) or if(condition)(action)else(action) 0020 % monitor monitor * (previously: monitor name=expression) 0021 % linker {'+=','-=','*=','/='} ('=>'for backwards compatibility) {'>-','>+','>*',or '>\'} 0022 % comment % or # 0023 % 0024 % Notes: 0025 % - Output "class" will be a cell array of strings if STRING contains 0026 % multiple expressions; otherwise it will be a string. 0027 % - This function is designed to be an internal helper function 0028 % called by user-level functions in DynaSim. 0029 % 0030 % Examples: 0031 % class=dsClassifyEquation('dx/dt=3*a*x') 0032 % classes=dsClassifyEquation('dx/dt=3*a*x; x(0)=0') 0033 % classes=dsClassifyEquation('dx/dt=3*a*x, x(0)=0',',') 0034 % classes=dsClassifyEquation('a=2; b=2*a; f(x)=b; dx/dt=f(x); x(0)=0; if(x>1)(x=0); current=>f(x); monitor f(x); % comments') 0035 % classes=dsClassifyEquation('model.eqns'); 0036 % 0037 % See also: dsParseModelEquations 0038 % 0039 % Author: Jason Sherfey, PhD <jssherfey@gmail.com> 0040 % Copyright (C) 2016 Jason Sherfey, Boston University, USA 0041 0042 %% localfn output 0043 if ~nargin 0044 output = localfunctions; % output var name specific to this fn 0045 return 0046 end 0047 0048 %% auto_gen_test_data_flag argin 0049 options = dsCheckOptions(varargin,{'auto_gen_test_data_flag',0,{0,1}},false); 0050 if options.auto_gen_test_data_flag 0051 varargs = varargin; 0052 varargs{find(strcmp(varargs, 'auto_gen_test_data_flag'))+1} = 0; 0053 varargs(end+1:end+2) = {'unit_test_flag',1}; 0054 argin = [{string},{delimiter}, varargs]; % specific to this function 0055 end 0056 0057 0058 %% check inputs 0059 if nargin==1, delimiter=';'; end % set default delimiter 0060 0061 if ~ischar(string) % error handling 0062 error('input must be string containing equations'); 0063 end 0064 0065 if exist(string,'file') 0066 % load equations from file and concatenate into a single string 0067 string=readtext(string); 0068 string=[string{:}]; % concatenate text from all lines 0069 end 0070 0071 %% split string on delimiter; remove insignificant white space & delimiters 0072 %strings=strtrim(splitstr(string,delimiter)); 0073 strings=strtrim(regexp(string,delimiter,'split')); 0074 strings=strrep(strings,delimiter,''); 0075 0076 % classify each delimited expression in string 0077 classes=cell(1,length(strings)); 0078 for i=1:length(strings) 0079 classes{i} = classify(strings{i}, varargin{:}); 0080 end 0081 0082 if length(classes)==1 % check for single expression 0083 classes=classes{1}; % return class label as string 0084 end 0085 0086 %% auto_gen_test_data_flag argout 0087 if options.auto_gen_test_data_flag 0088 argout = {classes}; % specific to this function 0089 0090 dsUnitSaveAutoGenTestData(argin, argout); 0091 end 0092 0093 end % main fn 0094 0095 0096 %% local functions 0097 0098 function class = classify(string, varargin) 0099 % input: string containing only one expression 0100 % output: class label (string) 0101 0102 %% auto_gen_test_data_flag argin 0103 options = dsCheckOptions(varargin,{'auto_gen_test_data_flag',0,{0,1}},false); 0104 if options.auto_gen_test_data_flag 0105 varargs = varargin; 0106 varargs{find(strcmp(varargs, 'auto_gen_test_data_flag'))+1} = 0; 0107 varargs(end+1:end+2) = {'unit_test_flag',1}; 0108 argin = [{string}, varargs]; % specific to this function 0109 end 0110 0111 class=''; 0112 0113 if isempty(string) 0114 % null check 0115 class='null'; 0116 elseif string(1)=='%' || string(1)=='#' 0117 % comment check 0118 class='comment'; 0119 end 0120 0121 % linker check: % [link ]? target operation expression 0122 % DynaSim-linker (matlab-incompatible) character combinations 0123 pattern='(link\s*)?((\+=)|(\-=)|(\*=)|(/=)|(=>))'; 0124 if isempty(class) && ~isempty(regexp(string,pattern,'once')) 0125 class='linker'; 0126 end 0127 0128 % ODE check: x'=expression or dx/dt=expression 0129 pattern='^((\w+'')|(d\w+/dt))\s*='; 0130 if isempty(class) && ~isempty(regexp(string,pattern,'once')) 0131 class='ODE'; 0132 end 0133 0134 % IC check: x(0)=expression 0135 pattern='^\w+\(0\)\s*='; 0136 if isempty(class) && ~isempty(regexp(string,pattern,'once')) 0137 class='IC'; 0138 end 0139 0140 % parameter check: var=expression (string or numeric) 0141 %pattern='^(([\w\.]+)|(\[\w+\]))\s*=\s*((''.*'')|(\[?[\d\.-(Inf)(inf)]+\]?))$'; 0142 % TODO: support scientific notation 0143 pattern='^(([\w\.]+)|(\[\w+\]))\s*=\s*((''.*'')|(\[?[+\d\.\-(Inf)(inf)]+\]?)|(\d+e[\-\+]?\d+))$'; 0144 if isempty(class) && ~isempty(regexp(string,pattern,'once')) 0145 class='parameter'; 0146 end 0147 0148 % conditional check: if(conditions)(actions)(else) 0149 pattern='^if\s*\(.+\)\s*\(.+\)'; 0150 if isempty(class) && ~isempty(regexp(string,pattern,'once','ignorecase')) 0151 class='conditional'; 0152 end 0153 0154 % function check: f(vars)=exression 0155 pattern='^\w+\([@a-zA-Z][\w,@]*\)\s*='; 0156 if isempty(class) && ~isempty(regexp(string,pattern,'once')) 0157 class='function'; 0158 end 0159 0160 % monitor check: monitor f=(expression or function) 0161 pattern='monitor .*'; 0162 if isempty(class) && ~isempty(regexp(string,pattern,'once')) 0163 class='monitor'; 0164 end 0165 0166 % fixed_variable (with indexing) check: var(#), var([#]), var([# #]), var([#,#]), var(#:#), var(#:end), var([#:#]), var([#:end]) 0167 pattern='^\w+\([\(\[?[\d\s,]+\]?\) | \(\[?\d+:[\(\d+\)|\(end\)]\]?\)]+\)'; % fixed with indexing: var(#), var([#]), var([# #]), var([#,#]), var(#:#), var(#:end), var([#:#]), var([#:end]) 0168 if isempty(class) && ~isempty(regexp(string,pattern,'once')) 0169 class='fixed_variable'; 0170 end 0171 0172 % fixed_variable (without indexing) check: var=(expression with grouping or arithmetic) 0173 pattern='^((\w+)|(\[\w+\]))\s*='; 0174 if isempty(class) && ~isempty(regexp(string,pattern,'once')) 0175 pattern1='(.*[a-z_A-Z,<>(<=)(>=)]+.*)$'; % rhs contains: []{}(),<>*/| % '=\s*.*[a-z_A-Z,<>(<=)(>=)]+.*' 0176 pattern2='=\s*\d+e[\-\+]?\d+$'; % scientific notation (should be classified as parameter, not fixed_variable) 0177 if ~isempty(regexp(string,pattern1,'once')) && ... 0178 isempty(regexp(string,pattern2,'once')) 0179 class='fixed_variable'; 0180 end 0181 end 0182 0183 if isempty(class) 0184 class='unclassified'; 0185 end 0186 0187 %% auto_gen_test_data_flag argout 0188 if options.auto_gen_test_data_flag 0189 argout = {class}; % specific to this function 0190 0191 dsUnitSaveAutoGenTestDataLocalFn(argin, argout); % localfn 0192 end 0193 0194 end %fn