Home > functions > internal > dsApplyModifications.m

dsApplyModifications

PURPOSE ^

APPLYMODIFICATIONS - Apply modifications to DynaSim specification or model structure

SYNOPSIS ^

function [output,modifications] = dsApplyModifications(model, modifications, varargin)

DESCRIPTION ^

APPLYMODIFICATIONS - Apply modifications to DynaSim specification or model structure

 dsApplyModifications returns the same kind of structure with
 modifications applied. In all cases it first modifies the specification
 (model.specification if input is a model structure). Then it returns
 the modified specification or regenerates the model using the new
 specification.

 Inputs:
   - model: DynaSim model or specification structure
     - see dsCheckModel and dsCheckSpecification for details
   - modifications: modifications to make to specification structure
       {X,Y,Z; X,Y,Z; ...}
       X = population name or connection source->target
       Y = thing to modify ('name', 'size', or parameter name)
       set Y=Z if Y = name, size, or value
       Note: (X1,X2) or (Y1,Y2): modify these simultaneously in the same way
       Note: Z can be a scalar, row vector, column vector, or matrix. Columns
       of Z are applied across items Y1, Y2, etc (e.g. parameters); rows of
       Z are applied to X1, X2, etc (e.g. population names). See examples
       in dsVary2Modifications.

 Outputs:
   - TODO

 Examples:
   - modifying population size and parameters:
       modifications={'E','size',5; 'E','gNa',120};
       model=dsApplyModifications(model,modifications);

   - modifying mechanism_list:
       m=dsApplyModifications('dv/dt=10+@current; {iNa,iK}',...
                            {'pop1','mechanism_list','-iNa'});
       m.populations.mechanism_list
       m=dsApplyModifications('dv/dt=10+@current; {iNa,iK}',...
                            {'pop1','mechanism_list','+iCa'});
       m.populations.mechanism_list
       m=dsApplyModifications('dv/dt=10+@current; {iNa,iK}',...
                            {'pop1','mechanism_list','+(iCa,iCan,CaBuffer)'});
       m.populations.mechanism_list

   - modifying equations (using special "cat()" operator or direct substitution)
       m=dsApplyModifications('dv/dt=10+@current; {iNa,iK}',...
                            {'pop1','equations','cat(dv/dt,+I)'});
       m.populations.equations
       m=dsApplyModifications('dv/dt=I(t)+@current; I(t)=10; {iNa,iK}',...
                            {'pop1','equations','cat(I(t),+sin(2*pi*t))'});
       m.populations.equations
       m=dsApplyModifications('dv/dt=I(t)+@current; I(t)=10; {iNa,iK}',...
                            {'pop1','equations','dv/dt=10+@current'});
       m.populations.equations
       m.populations.mechanism_list

   - modifying equations with reserved keywords "ODEn" and "FUNCTIONn"
     - Note:
       'ODEn' = reserved keyword referencing the n-th ODE in equations
       'ODE' = aliases ODE1
       similarly: 'FUNCTIONn' and 'FUNCTION'

       m=dsApplyModifications('dv/dt=10+@current; {iNa,iK}',...
                            {'pop1','equations','cat(ODE,+I)'});
       m.populations.equations
       m=dsApplyModifications('dv/dt=10+@current; du/dt=-u; {iNa,iK}',...
                            {'pop1','equations','cat(ODE2,+I)'});
       m.populations.equations
       m=dsApplyModifications('dv/dt=I(t)+@current; I(t)=10; {iNa,iK}',...
                            {'pop1','equations','cat(FUNCTION,+sin(2*pi*t))'});
       m.populations.equations

 See also: dsGenerateModel, dsSimulate, dsVary2Modifications

 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 [output,modifications] = dsApplyModifications(model, modifications, varargin)
0002 %APPLYMODIFICATIONS - Apply modifications to DynaSim specification or model structure
0003 %
0004 % dsApplyModifications returns the same kind of structure with
0005 % modifications applied. In all cases it first modifies the specification
0006 % (model.specification if input is a model structure). Then it returns
0007 % the modified specification or regenerates the model using the new
0008 % specification.
0009 %
0010 % Inputs:
0011 %   - model: DynaSim model or specification structure
0012 %     - see dsCheckModel and dsCheckSpecification for details
0013 %   - modifications: modifications to make to specification structure
0014 %       {X,Y,Z; X,Y,Z; ...}
0015 %       X = population name or connection source->target
0016 %       Y = thing to modify ('name', 'size', or parameter name)
0017 %       set Y=Z if Y = name, size, or value
0018 %       Note: (X1,X2) or (Y1,Y2): modify these simultaneously in the same way
0019 %       Note: Z can be a scalar, row vector, column vector, or matrix. Columns
0020 %       of Z are applied across items Y1, Y2, etc (e.g. parameters); rows of
0021 %       Z are applied to X1, X2, etc (e.g. population names). See examples
0022 %       in dsVary2Modifications.
0023 %
0024 % Outputs:
0025 %   - TODO
0026 %
0027 % Examples:
0028 %   - modifying population size and parameters:
0029 %       modifications={'E','size',5; 'E','gNa',120};
0030 %       model=dsApplyModifications(model,modifications);
0031 %
0032 %   - modifying mechanism_list:
0033 %       m=dsApplyModifications('dv/dt=10+@current; {iNa,iK}',...
0034 %                            {'pop1','mechanism_list','-iNa'});
0035 %       m.populations.mechanism_list
0036 %       m=dsApplyModifications('dv/dt=10+@current; {iNa,iK}',...
0037 %                            {'pop1','mechanism_list','+iCa'});
0038 %       m.populations.mechanism_list
0039 %       m=dsApplyModifications('dv/dt=10+@current; {iNa,iK}',...
0040 %                            {'pop1','mechanism_list','+(iCa,iCan,CaBuffer)'});
0041 %       m.populations.mechanism_list
0042 %
0043 %   - modifying equations (using special "cat()" operator or direct substitution)
0044 %       m=dsApplyModifications('dv/dt=10+@current; {iNa,iK}',...
0045 %                            {'pop1','equations','cat(dv/dt,+I)'});
0046 %       m.populations.equations
0047 %       m=dsApplyModifications('dv/dt=I(t)+@current; I(t)=10; {iNa,iK}',...
0048 %                            {'pop1','equations','cat(I(t),+sin(2*pi*t))'});
0049 %       m.populations.equations
0050 %       m=dsApplyModifications('dv/dt=I(t)+@current; I(t)=10; {iNa,iK}',...
0051 %                            {'pop1','equations','dv/dt=10+@current'});
0052 %       m.populations.equations
0053 %       m.populations.mechanism_list
0054 %
0055 %   - modifying equations with reserved keywords "ODEn" and "FUNCTIONn"
0056 %     - Note:
0057 %       'ODEn' = reserved keyword referencing the n-th ODE in equations
0058 %       'ODE' = aliases ODE1
0059 %       similarly: 'FUNCTIONn' and 'FUNCTION'
0060 %
0061 %       m=dsApplyModifications('dv/dt=10+@current; {iNa,iK}',...
0062 %                            {'pop1','equations','cat(ODE,+I)'});
0063 %       m.populations.equations
0064 %       m=dsApplyModifications('dv/dt=10+@current; du/dt=-u; {iNa,iK}',...
0065 %                            {'pop1','equations','cat(ODE2,+I)'});
0066 %       m.populations.equations
0067 %       m=dsApplyModifications('dv/dt=I(t)+@current; I(t)=10; {iNa,iK}',...
0068 %                            {'pop1','equations','cat(FUNCTION,+sin(2*pi*t))'});
0069 %       m.populations.equations
0070 %
0071 % See also: dsGenerateModel, dsSimulate, dsVary2Modifications
0072 %
0073 % Author: Jason Sherfey, PhD <jssherfey@gmail.com>
0074 % Copyright (C) 2016 Jason Sherfey, Boston University, USA
0075 
0076 %% localfn output
0077 if ~nargin
0078   output = localfunctions; % output var name specific to this fn
0079   return
0080 end
0081 
0082 %% auto_gen_test_data_flag argin
0083 options = dsCheckOptions(varargin,{'auto_gen_test_data_flag',0,{0,1}},false);
0084 if options.auto_gen_test_data_flag
0085   varargs = varargin;
0086   varargs{find(strcmp(varargs, 'auto_gen_test_data_flag'))+1} = 0;
0087   varargs(end+1:end+2) = {'unit_test_flag',1};
0088   argin = [{model},{modifications}, varargs]; % specific to this function
0089 end
0090 
0091 %%
0092 % check for modifications
0093 if isempty(modifications)
0094   % nothing to do
0095   output = model;
0096   return;
0097 end
0098 
0099 % check input type
0100 if ~isfield(model,'state_variables')
0101   ismodel = 0;
0102 else
0103   ismodel = 1;
0104 end
0105 
0106 % check specification
0107 if ismodel
0108   specification=dsCheckSpecification(model.specification, varargin{:});
0109 else
0110   specification=dsCheckSpecification(model, varargin{:});
0111 end
0112 
0113 % update specification with whatever is in modifications
0114 modifications = standardize_modifications(modifications,specification,varargin{:});
0115 % if ismodel % TODO: test this
0116   specification = modify_specification(specification,modifications,varargin{:});
0117 % end
0118 
0119 % update model if input was a model structure
0120 if ismodel
0121   output=dsGenerateModel(specification);
0122 else
0123   output = specification;
0124 end
0125 
0126 
0127 %% auto_gen_test_data_flag argout
0128 if options.auto_gen_test_data_flag
0129   argout = {output, modifications}; % specific to this function
0130 
0131   dsUnitSaveAutoGenTestData(argin, argout);
0132 end
0133 
0134 end
0135 
0136 function modifications = standardize_modifications(modifications,specification, varargin)
0137 % convert all modifications into 3-column cell matrix format
0138 % (namespace,variable,value)
0139 
0140 % 1. modifications structure
0141 % 2. partial specification structure
0142 
0143 %% auto_gen_test_data_flag argin
0144 options = dsCheckOptions(varargin,{'auto_gen_test_data_flag',0,{0,1}},false);
0145 if options.auto_gen_test_data_flag
0146   varargs = varargin;
0147   varargs{find(strcmp(varargs, 'auto_gen_test_data_flag'))+1} = 0;
0148   varargs(end+1:end+2) = {'unit_test_flag',1};
0149   argin = [{modifications},{specification}, varargs]; % specific to this function
0150 end
0151 
0152 if isstruct(modifications)
0153   % TODO
0154   % convert structure to cell matrix
0155   % ...
0156 end
0157 
0158 % check backward compatibility
0159 modifications=backward_compatibility(modifications);
0160 
0161 % check for empty object specification
0162 missing_objects=find(cellfun(@isempty,modifications(:,1)));
0163 if any(missing_objects)
0164   % set to default population name
0165   for i=1:length(missing_objects)
0166     modifications{missing_objects(i),1}=specification.populations(1).name;%'pop1';
0167   end
0168 end
0169 
0170 % support modifying multiple elements (of namespace or variable) simultaneously
0171 % approach -- add extra entry for each thing to modify
0172 % eg) expand {'(E,I)','Cm',2} to {'E','Cm',2; 'I','Cm',2}
0173 % note: should be able to support {'(E,I)','(EK,EK2)',-80}
0174 if any(~cellfun(@isempty,regexp(modifications(:,1),'^\(.*\)$'))) || ...
0175    any(~cellfun(@isempty,regexp(modifications(:,2),'^\(.*\)$')))
0176 
0177   % loop over modifications
0178   modifications_={};
0179   for i=1:size(modifications,1)
0180     % check namespace for ()
0181     namespaces=regexp(modifications{i,1},'[\w\.\-<>]+','match');
0182 
0183     % check variable for ()
0184     variables=regexp(modifications{i,2},'[\w\.-]+','match');
0185 
0186     if ischar(modifications{i,3})
0187 
0188         % expand list of modifications
0189         for j=1:length(namespaces)
0190             for k=1:length(variables)
0191                 modifications_(end+1,1:3)={namespaces{j},variables{k},modifications{i,3}};
0192             end
0193         end
0194 
0195     elseif isnumeric(modifications{i,3})
0196 
0197         % check size of values matches number of namespaces, variables
0198         if isscalar(modifications{i,3}) % in case number of values is one
0199             modifications{i,3} = repmat(modifications{i,3},length(variables),length(namespaces));
0200         else
0201             if size(modifications{i,3},1) ~= length(variables) || size(modifications{i,3},2) ~= length(namespaces)
0202                 % in case values is number of variables x 1
0203                 if size(modifications{i,3},1) == length(variables) && size(modifications{i,3},2) == 1
0204                     modifications{i,3} = repmat(modifications{i,3},1,length(namespaces));
0205                     % in case values is 1 x number of namespaces
0206                 elseif size(modifications{i,3},2) == length(namespaces) && size(modifications{i,3},1) == 1
0207                     modifications{i,3} = repmat(modifications{i,3},length(variables),1);
0208                     % TODO: char inputs
0209                     % elseif ischar(modifications{i,3})
0210                     % string input
0211                 else % if ~ischar(modifications{i,3})
0212                     error(['Numerical values varied over must be in array format,',...
0213                         'where dimensions 1, 2, and 3 correspond to mechanisms, values, and populations varied over.'])
0214                 end
0215             end
0216         end
0217 
0218         % expand list of modifications
0219         for j=1:length(namespaces)
0220             for k=1:length(variables)
0221                 modifications_(end+1,1:3)={namespaces{j},variables{k},modifications{i,3}(k,j)};
0222             end
0223         end
0224 
0225     end
0226 
0227   end
0228 
0229   modifications=modifications_;
0230 
0231 end
0232 
0233 %% auto_gen_test_data_flag argout
0234 if options.auto_gen_test_data_flag
0235   argout = {modifications}; % specific to this function
0236 
0237   dsUnitSaveAutoGenTestDataLocalFn(argin, argout); % localfn
0238 end
0239 
0240 end
0241 
0242 
0243 function spec = modify_specification(spec,mods, varargin)
0244 %% auto_gen_test_data_flag argin
0245 options = dsCheckOptions(varargin,{'auto_gen_test_data_flag',0,{0,1}},false);
0246 if options.auto_gen_test_data_flag
0247   varargs = varargin;
0248   varargs{find(strcmp(varargs, 'auto_gen_test_data_flag'))+1} = 0;
0249   varargs(end+1:end+2) = {'unit_test_flag',1};
0250   argin = [{spec},{mods}, varargs]; % specific to this function
0251 end
0252 
0253 precision=8; % number of digits allowed for user-supplied values
0254 predefined_variables={'name','size','parameters','mechanism_list','equations'};
0255 pop_names={spec.populations.name};
0256 if ~isempty(spec.connections)
0257   con_names=arrayfun(@(x)[x.source '->' x.target],spec.connections,'uni',0);
0258 else
0259   con_names=[];
0260 end
0261 
0262 % loop over modifications to apply
0263 for i=1:size(mods,1)
0264   obj=mods{i,1}; % population name or connection source-target
0265   fld=mods{i,2}; % population name, size, or parameter name
0266 
0267   if ~ischar(obj)
0268     error('modification must be applied to population name or connection source-target');
0269   end
0270 
0271   if ~ischar(fld) %|| ~ismember(fld,{'name','size','parameters','mechanism_list','equations'})
0272     error('modification must be applied to population ''name'',''size'',or a parameter referenced by its name');
0273   end
0274 
0275   % standardize connection object: convert target<-source to source->target
0276   if any(strfind(obj,'<-'))
0277     ind=strfind(obj,'<-');
0278     obj=[obj(ind(1)+2:end) '->' obj(1:ind(1)-1)];
0279   end
0280 
0281   % check and adjust for mechanism-specific parameter identifier
0282 %   MECH='';
0283   if any(obj=='.') % OBJECT.MECH
0284     tmp=regexp(obj,'\.','split');
0285     obj=tmp{1};
0286     MECH=tmp{2};
0287     fld=[MECH '.' fld];
0288   end
0289 %   if any(fld=='.') % MECH.PARAM
0290 %     tmp=regexp(fld,'\.','split');
0291 %     MECH=tmp{1};
0292 %     fld=tmp{2};
0293 %   end
0294 %   if ~isempty(MECH)
0295 %     fld=[MECH '.' fld];
0296 %   end
0297 
0298   val=mods{i,3}; % value for population name or size, or parameter to modify
0299   if ismember(obj,pop_names)
0300     type='populations';
0301     names=pop_names;
0302   elseif ismember(obj,con_names)
0303     type='connections';
0304     names=con_names;
0305   else
0306     warning('name of object to modify not found in populations or connections.');
0307     continue
0308   end
0309 
0310   index=ismember(names,obj);
0311 
0312   if strcmp(fld,'mechanism_list')
0313     % support --
0314     % 'E'    'mechanism_list'    '(iNa,iK)'
0315     % 'E'    'mechanism_list'    '-(iNa,iK)'
0316     % 'E'    'mechanism_list'    '+(iK)'
0317     % 'E'    'mechanism_list'    '+iK'
0318     % 'E'    'mechanism_list'    'iK'
0319 
0320     elems=regexp(val,'[\w@]+','match');
0321 
0322     if strcmp(val(1),'+')
0323       % add mechanisms to existing list
0324       spec.(type)(index).mechanism_list=unique_wrapper(cat(2,spec.(type)(index).mechanism_list,elems),'stable');
0325     elseif strcmp(val(1),'-')
0326       % remove mechanisms from existing list
0327       spec.(type)(index).mechanism_list=setdiff(spec.(type)(index).mechanism_list,elems,'stable');
0328     else
0329       % redefine mechanism list
0330       spec.(type)(index).mechanism_list=elems;
0331     end
0332   elseif strcmp(fld,'equations') && ~isempty(regexp(val,'^cat(','once'))
0333     % process special case: cat(TARGET,EXPRESSION)
0334     eqns=spec.(type)(index).equations;
0335     args=regexp(val,'cat\((.+)\)','tokens','once');
0336     ind=find(args{1}==',',1,'first');
0337     target=args{1}(1:ind-1);
0338     expression=args{1}(ind+1:end);
0339 %     args=regexp(args{1},',','split');
0340 %     target=args{1};
0341 %     expression=args{2};
0342     % support keywords: ODEn, ODE=ODE1, FUNCTIONn, FUNCTION=FUNCTION1
0343     if strncmp('ODE',target,3)
0344       % support target = ODEn and ODE=ODE1
0345       % replace target by n-th ODE LHS
0346       lines=cellfun(@strtrim,strsplit(eqns,';'),'uni',0); % split equations into statements
0347       lines=lines(~cellfun(@isempty,lines)); % eliminate empty elements
0348       pattern='^((\w+'')|(d\w+/dt))\s*='; % pattern for ODEs
0349       inds=regexp(lines,pattern,'once'); % indices to ODE statements
0350       inds=find(~cellfun(@isempty,inds));
0351       % get index to the ODE statement to modify
0352       if strcmp(target,'ODE')
0353         ind=inds(1);
0354       else
0355         n=str2num(target(4:end));
0356         ind=inds(n);
0357       end
0358       % get LHS of ODE
0359       %LHS=regexp(lines{ind},'^(.+)=','tokens','once');
0360       LHS=regexp(lines{ind},'^([^=]+)=','tokens','once');
0361       target=LHS{1};
0362     elseif strncmp('FUNCTION',target,8)
0363       % support target = FUNCTIONn and FUNCTION=FUNCTION1
0364       % replace target by n-th FUNCTION LHS
0365       lines=cellfun(@strtrim,strsplit(eqns,';'),'uni',0); % split equations into statements
0366       lines=lines(~cellfun(@isempty,lines)); % eliminate empty elements
0367       pattern='^\w+\([a-zA-Z][\w,]*\)\s*='; % pattern for functions
0368       inds=regexp(lines,pattern,'once'); % indices to function statements
0369       inds=find(~cellfun(@isempty,inds));
0370 
0371       % get index to the function statement to modify
0372       if strcmp(target,'FUNCTION')
0373         ind=inds(1);
0374       else
0375         n=str2num(target(9:end));
0376         ind=inds(n);
0377       end
0378       % get LHS of ODE
0379       %LHS=regexp(lines{ind},'^(.+)=','tokens','once');
0380       LHS=regexp(lines{ind},'^([^=]+)=','tokens','once');
0381       target=LHS{1};
0382     end
0383     % add escape character for using regular expression to match function statements
0384     target=strrep(target,'(','\(');
0385     target=strrep(target,')','\)');
0386 
0387     % modify equations
0388     old=regexp(eqns,[target '\s*=[^;]+'],'match');
0389     if ~isempty(old)
0390       eqns=strrep(eqns,old{1},[old{1} expression]);
0391       spec.(type)(index).equations=eqns;
0392     end
0393   elseif ismember(fld,predefined_variables) % not a single parameter to modify
0394     spec.(type)(index).(fld)=val;
0395     if strcmp(type,'populations') && strcmp(fld,'name')
0396       % update name in connections
0397       for j=1:length(spec.connections)
0398         if strcmp(pop_names{index},spec.connections(j).source)
0399           spec.connections(j).source=val;
0400         end
0401 
0402         if strcmp(pop_names{index},spec.connections(j).target)
0403           spec.connections(j).target=val;
0404         end
0405       end
0406     end
0407   else % modify a single parameter in the populations.parameters cell array
0408     param_names=spec.(type)(index).parameters(1:2:end);
0409     if isempty(spec.(type)(index).parameters)
0410       % no parameters set; start cell array of parameters
0411       spec.(type)(index).parameters={fld,val};%toString(val2,precision)};
0412     elseif ismember(fld,param_names)
0413       % parameter found in list; update its value
0414       val_pos=2*find(ismember(param_names,fld));
0415       spec.(type)(index).parameters{val_pos}=val;%toString(val2,precision);
0416     else
0417       % parameter not in existing list; append to end
0418       spec.(type)(index).parameters{end+1}=fld;
0419       spec.(type)(index).parameters{end+1}=val;%toString(val2,precision);
0420     end
0421   end
0422 end
0423 
0424 % todo: split X.MECH, set (type,index) from X, store {MECH.fld,val} in .parameters
0425 
0426 %% auto_gen_test_data_flag argout
0427 if options.auto_gen_test_data_flag
0428   argout = {spec}; % specific to this function
0429 
0430   dsUnitSaveAutoGenTestDataLocalFn(argin, argout); % localfn
0431 end
0432 
0433 end
0434 
0435 
0436 function modifications=backward_compatibility(modifications)
0437 % convert 2-column specification to 3-column specification with empty object name
0438 if size(modifications,2)==2
0439   tmp={};
0440   for i=1:size(modifications,1)
0441     tmp{i,1}='';
0442     tmp{i,2}=modifications{i,1};
0443     tmp{i,3}=modifications{i,2};
0444   end
0445   modifications=tmp;
0446 end
0447 
0448 % convert 4-column specification to 3-column
0449 if size(modifications,2)==4
0450   for i=1:size(modifications,1)
0451     if strcmp(modifications{i,2},'parameters')
0452       % shift parameter name to 2nd column
0453       modifications{i,2}=modifications{i,3};
0454 
0455       % shift parameter value to 3rd column
0456       modifications{i,3}=modifications{i,4};
0457     end
0458   end
0459 
0460   % remove fourth column
0461   modifications=modifications(:,1:3);
0462 end
0463 
0464 % convert connection reference source-target to source->target
0465 if any(~cellfun(@isempty,regexp(modifications(:,1),'\w-\w')))
0466   % find elements to adjust
0467   inds=find(~cellfun(@isempty,regexp(modifications(:,1),'\w-\w')));
0468   for i=1:length(inds)
0469     modifications{inds(i),1}=strrep(modifications{inds(i),1},'-','->');
0470   end
0471 end
0472 
0473 end

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