Home > functions > internal > dsGenerateModel.m

dsGenerateModel

PURPOSE ^

GENERATEMODEL - Parse DynaSim specification and organize model data in DynaSim model structure

SYNOPSIS ^

function [model,name_map] = dsGenerateModel(specification, varargin)

DESCRIPTION ^

GENERATEMODEL - Parse DynaSim specification and organize model data in DynaSim model structure

 Usage:
   [model,name_map]=dsGenerateModel(specification,'option',value,...)

 Inputs:
   - specification: one of:
     - DynaSim specification structure (see below and dsCheckSpecification for more details)
     - string with name of MAT-file containing DynaSim specification structure
     - string with equations
     - string with name of file containing equations (.eqns)
       note: .eqns files can also be converted into model structure using LoadModel()
   - options (with defaults): 'option1',value1,'option2',value2,...
     'modifications'  : specify modifications to apply to specification
                        before generating the model, see dsApplyModifications
                        for more details (default?: []).
     'open_link_flag' : whether to leave linker identifiers in place (default: 0)
     'auto_gen_test_data_flag': whether to save model for unit testing (default: 0)

 Outputs:
   - model: DynaSim model structure (see dsCheckModel for more details):
     .parameters      : substructure with model parameters
     .fixed_variables : substructure with fixed variable definitions
     .functions       : substructure with function definitions
     .monitors        : substructure with monitor definitions
     .state_variables : cell array listing state variables
     .ODEs            : substructure with one ordinary differential
                             equation (ODE) per state variable
     .ICs             : substructure with initial conditions (ICs) for
                             each state variable
     .conditionals(i) : structure array with each element indicating
                             conditional actions specified in subfields
                             "condition","action","else" (see NOTE 1 in dsCheckModel)
     .linkers(i)      : structure array with each element indicating
                             an "expression" that should be inserted
                             (according to "operation") into any equations
                             where the "target" appears. (see NOTE 2 in dsCheckModel)
       .target    : string giving the target where expression should be inserted
       .expression: string giving the expression to insert
       .operation : string giving the operation to use to insert expression
     .comments{i}     : cell array of comments found in model files
     .specification   : specification used to generate the model
     .namespaces      : (see NOTE 3 in dsCheckModel)
   - name_map: cell matrix mapping parameter, variable, and function names
       between the user-created specification (population equations and mechanism
       files) and the full model with automatically generated namespaces. It
       has four columns with: user-specified name, name with namespace prefix,
       namespace, and type ('parameters', 'fixed_variables', 'state_variables',
       'functions', or 'monitors') indicating the category to which the named
       element belongs.

 - DynaSim specification structure (see dsCheckSpecification for more details)
   .populations(i) (required): contains info for defining independent population models
       .name (default: 'pop1')      : name of population
       .size (default: 1)           : number of elements in population (i.e., # cells)
       .equations (required)        : string listing equations (see NOTE 1 in dsCheckSpecification)
       .mechanism_list (default: []): cell array listing mechanisms (see NOTE 2
                                      in dsCheckSpecification)
       .parameters (default: [])    : parameters to assign across all equations in
         the population. provide as cell array list of key/value pairs
         {'param1',value1,'param2',value2,...}
       .model (default: [])   : optional DynaSim model structure
   .connections(i) (default: []): contains info for linking population models
       .source (required if >1 pops): name of OUT population
       .target (required if >1 pops): name of target population
       .mechanism_list (required)   : list of mechanisms that link two populations
       .parameters (default: [])    : parameters to assign across all equations in
         mechanisms of this connection's mechanism_list.

 Examples:
   - Example 0:
     model=dsGenerateModel('db/dt=3')

   - Example 1: Lorenz equations
     eqns={
       's=10; r=27; b=2.666';
       'dx/dt=s*(y-x)';
       'dy/dt=r*x-y-x*z';
       'dz/dt=-b*z+x*y';
     };
     model=dsGenerateModel(eqns)

   - Example 2: Leaky integrate-and-fire neuron
     model=dsGenerateModel('tau=10; R=10; E=-70; dV/dt=(E-V+R*1.55)/tau; if(V>-55)(V=-75)')

   - Example 3: Hodgkin-Huxley neuron with sinusoidal drive
     model=dsGenerateModel('dv/dt=current+sin(2*pi*t); {iNa,iK}')

   - Example 4: HH with self inhibition and sinusoidal drive
     specification.populations(1).equations='dv/dt=current+sin(2*pi); v(0)=-65';
     specification.populations(1).mechanism_list={'iNa','iK'};
     specification.connections(1).mechanism_list={'iGABAa'};
     specification.connections(1).parameters={'tauDx',15};
     model=dsGenerateModel(specification)

   - Example 5: using custom mechanism alias in equations (for modularization)
     specification.populations(1).equations='dv/dt=@M+sin(2*pi); v(0)=-65';
     specification.populations(1).mechanism_list={'iNa@M','iK@M'};
     model=dsGenerateModel(specification)

     or:

     specification.populations(1).equations='dv/dt=@M+sin(2*pi); {iNa,iK}@M; v(0)=-65';
     model=dsGenerateModel(specification)

   - Example 6: directly incorporating mechanism models from online repositories:

     model=dsGenerateModel('dv/dt=@M; {ib:57,iK}@M')

     where "ib" is a known alias for the infinitebrain.org repository,
     and "57" is the Na+ current at http://infinitebrain.org/models/57.
     note: currently not supported on *most* machines...

 See also: dsCheckSpecification, dsCheckModel, dsParseModelEquations, dsSimulate

 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,name_map] = dsGenerateModel(specification, varargin)
0002 %GENERATEMODEL - Parse DynaSim specification and organize model data in DynaSim model structure
0003 %
0004 % Usage:
0005 %   [model,name_map]=dsGenerateModel(specification,'option',value,...)
0006 %
0007 % Inputs:
0008 %   - specification: one of:
0009 %     - DynaSim specification structure (see below and dsCheckSpecification for more details)
0010 %     - string with name of MAT-file containing DynaSim specification structure
0011 %     - string with equations
0012 %     - string with name of file containing equations (.eqns)
0013 %       note: .eqns files can also be converted into model structure using LoadModel()
0014 %   - options (with defaults): 'option1',value1,'option2',value2,...
0015 %     'modifications'  : specify modifications to apply to specification
0016 %                        before generating the model, see dsApplyModifications
0017 %                        for more details (default?: []).
0018 %     'open_link_flag' : whether to leave linker identifiers in place (default: 0)
0019 %     'auto_gen_test_data_flag': whether to save model for unit testing (default: 0)
0020 %
0021 % Outputs:
0022 %   - model: DynaSim model structure (see dsCheckModel for more details):
0023 %     .parameters      : substructure with model parameters
0024 %     .fixed_variables : substructure with fixed variable definitions
0025 %     .functions       : substructure with function definitions
0026 %     .monitors        : substructure with monitor definitions
0027 %     .state_variables : cell array listing state variables
0028 %     .ODEs            : substructure with one ordinary differential
0029 %                             equation (ODE) per state variable
0030 %     .ICs             : substructure with initial conditions (ICs) for
0031 %                             each state variable
0032 %     .conditionals(i) : structure array with each element indicating
0033 %                             conditional actions specified in subfields
0034 %                             "condition","action","else" (see NOTE 1 in dsCheckModel)
0035 %     .linkers(i)      : structure array with each element indicating
0036 %                             an "expression" that should be inserted
0037 %                             (according to "operation") into any equations
0038 %                             where the "target" appears. (see NOTE 2 in dsCheckModel)
0039 %       .target    : string giving the target where expression should be inserted
0040 %       .expression: string giving the expression to insert
0041 %       .operation : string giving the operation to use to insert expression
0042 %     .comments{i}     : cell array of comments found in model files
0043 %     .specification   : specification used to generate the model
0044 %     .namespaces      : (see NOTE 3 in dsCheckModel)
0045 %   - name_map: cell matrix mapping parameter, variable, and function names
0046 %       between the user-created specification (population equations and mechanism
0047 %       files) and the full model with automatically generated namespaces. It
0048 %       has four columns with: user-specified name, name with namespace prefix,
0049 %       namespace, and type ('parameters', 'fixed_variables', 'state_variables',
0050 %       'functions', or 'monitors') indicating the category to which the named
0051 %       element belongs.
0052 %
0053 % - DynaSim specification structure (see dsCheckSpecification for more details)
0054 %   .populations(i) (required): contains info for defining independent population models
0055 %       .name (default: 'pop1')      : name of population
0056 %       .size (default: 1)           : number of elements in population (i.e., # cells)
0057 %       .equations (required)        : string listing equations (see NOTE 1 in dsCheckSpecification)
0058 %       .mechanism_list (default: []): cell array listing mechanisms (see NOTE 2
0059 %                                      in dsCheckSpecification)
0060 %       .parameters (default: [])    : parameters to assign across all equations in
0061 %         the population. provide as cell array list of key/value pairs
0062 %         {'param1',value1,'param2',value2,...}
0063 %       .model (default: [])   : optional DynaSim model structure
0064 %   .connections(i) (default: []): contains info for linking population models
0065 %       .source (required if >1 pops): name of OUT population
0066 %       .target (required if >1 pops): name of target population
0067 %       .mechanism_list (required)   : list of mechanisms that link two populations
0068 %       .parameters (default: [])    : parameters to assign across all equations in
0069 %         mechanisms of this connection's mechanism_list.
0070 %
0071 % Examples:
0072 %   - Example 0:
0073 %     model=dsGenerateModel('db/dt=3')
0074 %
0075 %   - Example 1: Lorenz equations
0076 %     eqns={
0077 %       's=10; r=27; b=2.666';
0078 %       'dx/dt=s*(y-x)';
0079 %       'dy/dt=r*x-y-x*z';
0080 %       'dz/dt=-b*z+x*y';
0081 %     };
0082 %     model=dsGenerateModel(eqns)
0083 %
0084 %   - Example 2: Leaky integrate-and-fire neuron
0085 %     model=dsGenerateModel('tau=10; R=10; E=-70; dV/dt=(E-V+R*1.55)/tau; if(V>-55)(V=-75)')
0086 %
0087 %   - Example 3: Hodgkin-Huxley neuron with sinusoidal drive
0088 %     model=dsGenerateModel('dv/dt=current+sin(2*pi*t); {iNa,iK}')
0089 %
0090 %   - Example 4: HH with self inhibition and sinusoidal drive
0091 %     specification.populations(1).equations='dv/dt=current+sin(2*pi); v(0)=-65';
0092 %     specification.populations(1).mechanism_list={'iNa','iK'};
0093 %     specification.connections(1).mechanism_list={'iGABAa'};
0094 %     specification.connections(1).parameters={'tauDx',15};
0095 %     model=dsGenerateModel(specification)
0096 %
0097 %   - Example 5: using custom mechanism alias in equations (for modularization)
0098 %     specification.populations(1).equations='dv/dt=@M+sin(2*pi); v(0)=-65';
0099 %     specification.populations(1).mechanism_list={'iNa@M','iK@M'};
0100 %     model=dsGenerateModel(specification)
0101 %
0102 %     or:
0103 %
0104 %     specification.populations(1).equations='dv/dt=@M+sin(2*pi); {iNa,iK}@M; v(0)=-65';
0105 %     model=dsGenerateModel(specification)
0106 %
0107 %   - Example 6: directly incorporating mechanism models from online repositories:
0108 %
0109 %     model=dsGenerateModel('dv/dt=@M; {ib:57,iK}@M')
0110 %
0111 %     where "ib" is a known alias for the infinitebrain.org repository,
0112 %     and "57" is the Na+ current at http://infinitebrain.org/models/57.
0113 %     note: currently not supported on *most* machines...
0114 %
0115 % See also: dsCheckSpecification, dsCheckModel, dsParseModelEquations, dsSimulate
0116 %
0117 % Author: Jason Sherfey, PhD <jssherfey@gmail.com>
0118 % Copyright (C) 2016 Jason Sherfey, Boston University, USA
0119 
0120 % Check inputs
0121 % ------------------------------------------------------
0122 if nargin==0
0123   % use default model
0124   specification=[];
0125   specification.populations(1).equations='dv/dt=10+@current/Cm; Cm=1; v(0)=-65';
0126   specification.populations(1).mechanism_list={'iNa','iK'};
0127   specification.populations(1).parameters={'Cm',1};
0128   specification.connections(1).mechanism_list={'iGABAa'};
0129   varargin={'modifications',[]};
0130 end
0131 % ------------------------------------------------------
0132 
0133 options=dsCheckOptions(varargin,{...
0134   'modifications',[],[],...
0135   'open_link_flag',0,{0,1},...
0136   'auto_gen_test_data_flag',0,{0,1},...
0137   },false);
0138 
0139 if options.auto_gen_test_data_flag
0140   varargs = varargin;
0141   varargs{find(strcmp(varargs, 'auto_gen_test_data_flag'))+1} = 0;
0142   varargs(end+1:end+2) = {'unit_test_flag',1};
0143   argin = [{specification}, varargs]; % specific to this function
0144 end
0145 
0146 % check if a model
0147 if isfield(specification,'state_variables')
0148   % do nothing
0149   model=specification;
0150   return;
0151 %   TODO: consider the following --
0152 %   if isfield(specification,'specification')
0153 %     % regenerate from specification
0154 %     specification=specification.specification;
0155 %   end
0156 end
0157 % standardize specification
0158 specification=dsCheckSpecification(specification, varargin{:}); % standardize & auto-populate as needed
0159 
0160 % Apply modifications to specification before generating model
0161 if ~isempty(options.modifications)
0162   specification=dsApplyModifications(specification,options.modifications, varargin{:});
0163 end
0164 
0165 % specification metadata:
0166 npops=length(specification.populations); % number of populations
0167 ncons=length(specification.connections); % number of connections
0168 
0169 %{
0170 % Dev notes on improving implementation:
0171 % Ideally (1.0)-(3.0) could be packaged into external functions and run as:
0172 % -------------------------------------------------------------------------
0173 %% 1.0 load sub-models, assign namespaces, and combine across all equations and mechanisms in specification
0174 % [model,name_map]=LoadModelSet(specification) % bug: disrupted subsequent namespace propagation (without raising an error)
0175 %% 2.0 propagate namespaces through variable and function names
0176 % model = dsPropagateNamespaces(model,name_map); % this works
0177 %% 3.0 expand population equations according to mechanism linkers
0178 % model = LinkMechanisms(model,name_map);      % problem: unable to identify linker population from model.linkers; see notes below (3.0) for more details
0179 % -------------------------------------------------------------------------
0180 %}
0181 
0182 % support full modularization of mechanisms
0183 % (eg, dv/dt=@M; {Na,K}@M w/ Na.mech: @current += I(IN,m,h)).
0184 %     approach taken below:
0185 %     - add support for dv/dt=@M; {Na@M,K@M}
0186 %       have dsGenerateModel split mech_name on '@' and replace first
0187 %       linker in mech (e.g., @current) by what follows '@' (e.g., @M)
0188 %     - then have dsCheckSpecification convert {Na,K}@M into {Na@M,K@M}
0189 
0190 %% 1.0 load sub-models, assign namespaces, and combine across all equations and mechanisms in specification
0191 % use empty struct for Octave compatibility
0192 model.parameters=struct('');
0193 model.fixed_variables=struct('');
0194 model.functions=struct('');
0195 model.monitors=struct('');
0196 model.state_variables={};
0197 model.ODEs=struct('');
0198 model.ICs=struct('');
0199 model.conditionals=struct('');
0200 model.linkers=struct('');
0201 model.comments={};
0202 name_map={}; % {name, namespace_name, namespace, type}, used for namespacing
0203 linker_pops={}; % list of populations associated with mechanism linkers
0204 
0205 % 1.1 load and combine population sub-models from population equations and mechanisms
0206 for i=1:npops
0207   % does the population model already exist?
0208   if ~isempty(specification.populations(i).model)
0209     tmpmodel=specification.populations(i).model; % get model structure
0210     tmpname=tmpmodel.specification.populations.name; % assumes one population sub-model
0211 
0212     % adjust the name if necessary
0213     if ~strcmp(specification.populations(i).name,tmpname)
0214       % use the name in the specification
0215       tmpmodel=dsApplyModifications(tmpmodel,{tmpname,'name',specification.populations(i).name}, varargin{:});
0216     elseif strcmp(tmpname,'pop1') % if default name
0217       % use default name for this population index
0218       tmpmodel=dsApplyModifications(tmpmodel,{tmpname,'name',sprintf('pop%g',i)}, varargin{:});
0219     end
0220 
0221     tmpmodel.linkers=[]; % remove old linkers from original model construction
0222     model=dsCombineModels(model,tmpmodel, varargin{:});
0223     name_map=cat(1,name_map,tmpmodel.namespaces);
0224     continue;
0225   end
0226   % construct new population model
0227   PopScope=specification.populations(i).name;
0228     % NOTE: dsParseModelEquations adds a '_' suffix to the namespace; therefore,
0229     % a '_' suffix is added to PopScope when used below for consistency of
0230     % namespaces/namespaces. (this could be cleaned up by adding '_' to PopScope
0231     % here, removing it below, and removing the additional '_' from
0232     % dsParseModelEquations).
0233 
0234   % 1.1.1 parse population equations
0235   equations=specification.populations(i).equations;
0236   parameters=specification.populations(i).parameters;
0237   nmechs=length(specification.populations(i).mechanism_list);
0238 
0239   % parse population equations
0240   if ~isempty(equations)
0241     [tmpmodel,tmpmap]=dsImportModel(equations,'namespace',PopScope,'ic_pop',specification.populations(i).name,'user_parameters',parameters);
0242     model=dsCombineModels(model,tmpmodel, varargin{:});
0243     name_map=cat(1,name_map,tmpmap);
0244   end
0245 
0246   % 1.1.2 parse population mechanisms
0247   for j=1:nmechs
0248     mechanism_=specification.populations(i).mechanism_list{j};
0249 
0250     % support separation of linker names in pop equations vs mechanisms
0251     mechanism_=regexp(mechanism_,'@','split');
0252     mechanism=mechanism_{1};
0253 
0254     if numel(mechanism_)>1, new_linker=mechanism_{2}; else new_linker=[]; end
0255 
0256     % set mechanism namespace
0257     [~,MechID]=fileparts2(mechanism);
0258     if any(MechID==':')
0259       % exclude host name from namespace
0260       tmp=regexp(MechID,':','split');
0261       MechScope=[specification.populations(i).name '_' tmp{2}];
0262     else
0263       % extract mechanism file name without path
0264       MechScope=[specification.populations(i).name '_' MechID];
0265     end
0266     % use mechanism equations in specification if present
0267     if isfield(specification.populations,'mechanisms') && ~isempty(specification.populations(i).mechanisms)
0268       if ismember(MechID,{specification.populations(i).mechanisms.name})
0269         idx=ismember({specification.populations(i).mechanisms.name},MechID);
0270         mechanism=specification.populations(i).mechanisms(idx).equations;
0271       end
0272       % parse mechanism equations
0273       if ~isempty(mechanism)
0274         [tmpmodel,tmpmap]=dsImportModel(mechanism,'namespace',MechScope,'ic_pop',specification.populations(i).name,'user_parameters',parameters);
0275         % replace 1st linker name by the one in specification
0276         if ~isempty(new_linker) && ~isempty(tmpmodel.linkers)
0277           % first try to find 1st linker target starting with @
0278           links_at=find(~cellfun(@isempty,regexp({tmpmodel.linkers.target},'^@','once')));
0279           if ~isempty(links_at)
0280             % use first link with target prepended by '@'
0281             link_ind=links_at(1);
0282           else
0283             % use first link
0284             link_ind=1;
0285           end
0286           tmpmodel.linkers(link_ind).target=['@' new_linker];
0287         end
0288         % combine sub-model with other sub-models
0289         model=dsCombineModels(model,tmpmodel, varargin{:});
0290         name_map=cat(1,name_map,tmpmap);
0291         linker_pops=cat(2,linker_pops,repmat({specification.populations(i).name},[1 length(tmpmodel.linkers)]));
0292       end
0293     end
0294   end
0295   pop=specification.populations(i).name;
0296 
0297   % add reserved keywords (parameters and state variables) to name_map
0298   add_keywords(pop,pop,[PopScope '_']);
0299   %model.parameters.([pop '_Npop'])=num2str(specification.populations(i).size);
0300   model.parameters(1).([pop '_Npop'])=toString(specification.populations(i).size,0);
0301 end
0302 
0303 % 1.2 load and combine sub-models from connection mechanisms
0304 for i=1:ncons
0305   % parse connection mechanisms
0306   source=specification.connections(i).source;
0307   target=specification.connections(i).target;
0308   parameters=specification.connections(i).parameters;
0309   ConScope=[target '_' source '_'];
0310     % NOTE: in contrast to PopScope above, ConScope is never passed to
0311     % dsParseModelEquations; thus the '_' should be added here for consistency
0312     % with mechanism namespaces (which are modified by dsParseModelEquations).
0313   for j=1:length(specification.connections(i).mechanism_list)
0314     mechanism_=specification.connections(i).mechanism_list{j};
0315 
0316     % support separation of linker names in pop equations vs mechanisms
0317     mechanism_=regexp(mechanism_,'@','split');
0318     mechanism=mechanism_{1};
0319 
0320     if numel(mechanism_)>1, new_linker=mechanism_{2}; else new_linker=[]; end
0321 
0322     % extract mechanism file name without path
0323     [~,MechID]=fileparts2(mechanism);
0324     MechScope=[target '_' source '_' MechID];
0325         % NOTE: must use target_source_mechanism for connection mechanisms
0326         % to distinguish their parent namespaces from those of population mechanisms
0327         % see: dsGetParentNamespace
0328 
0329     % use mechanism equations in specification if present
0330     if ~isempty(specification.connections(i).mechanisms) && ismember(MechID,{specification.connections(i).mechanisms.name})
0331       idx=ismember({specification.connections(i).mechanisms.name},MechID);
0332       mechanism=specification.connections(i).mechanisms(idx).equations;
0333     end
0334 
0335     % parse model equations
0336     [tmpmodel,tmpmap]=dsImportModel(mechanism,'namespace',MechScope,'ic_pop',source,'user_parameters',parameters);
0337 
0338     % replace 1st linker name by the one in specification
0339     if ~isempty(new_linker) && ~isempty(tmpmodel.linkers)
0340       tmpmodel.linkers(1).target=['@' new_linker];
0341     end
0342 
0343     model=dsCombineModels(model,tmpmodel, varargin{:});
0344     name_map=cat(1,name_map,tmpmap);
0345 
0346     % link this mechanism to the target population
0347     linker_pops=cat(2,linker_pops,repmat(target,[1 length(tmpmodel.linkers)]));
0348 
0349     % edit names of connection monitors specified in population equations
0350     % TODO: consider design changes to avoid specifying connection monitors
0351     %   in population equations; this is an undesirable hack:
0352     %     eg, convert E_iGABAa_functions -> I_E_iGABAa_functions
0353     if ~isempty(model.monitors)
0354       % get indices to all model.monitors that have incorrect connection namespace
0355       con_mon_to_update=find(~cellfun(@isempty,regexp(fieldnames(model.monitors),['^' target '_' mechanism])));
0356       if any(con_mon_to_update)
0357         % get list of current model.monitors
0358         monitor_names=fieldnames(model.monitors);
0359         for m=1:length(con_mon_to_update)
0360           % get name of monitor with incorrect connection namespace
0361           old=monitor_names{con_mon_to_update(m)};
0362 
0363           % get name of monitor with correct connection namespace
0364           new=strrep(old,[target '_' mechanism '_'],[MechScope '_']);
0365 
0366           % add new monitor with correct namespace
0367           model.monitors.(new)=model.monitors.(old);
0368 
0369           % remove old monitor with incorrect namespace
0370           model.monitors=rmfield(model.monitors,old);
0371         end
0372       end
0373     end
0374   end
0375   % add reserved keywords (parameters and state variables) to name_map
0376   add_keywords(source,target,ConScope);
0377 end
0378 
0379 % check for monitoring functions (e.g., 'monitor functions' or 'monitor Na.functions')
0380 if ~isempty(model.monitors)
0381   % get list of functions
0382   if ~isempty(model.functions)
0383     function_names=fieldnames(model.functions);
0384   else
0385     function_names={};
0386   end
0387   % get list of monitor names
0388   monitor_names=fieldnames(model.monitors);
0389 
0390   % get indices to monitors with names ending in _functions
0391   function_monitor_index=find(~cellfun(@isempty,regexp(monitor_names,'_functions$','once')));
0392 
0393   % create list of functions with namespaces matching monitors ending in _functions
0394   functions_to_monitor={};
0395 
0396   for i=1:length(function_monitor_index)
0397     % get namespace of functions to monitor
0398     monitor_name=monitor_names{function_monitor_index(i)};
0399     monitor_namespace=regexp(monitor_name,'(.*)_functions$','tokens','once');
0400     monitor_namespace=monitor_namespace{1};
0401 
0402     % get list of functions with matching namespace
0403     function_index=find(~cellfun(@isempty,regexp(function_names,['^' monitor_namespace],'once')));
0404 
0405     % add functions to list
0406     functions_to_monitor=cat(1,functions_to_monitor,function_names(function_index));
0407 
0408     % remove "function" monitor name
0409     model.monitors=rmfield(model.monitors,monitor_name);
0410   end
0411 
0412   % eliminate duplicate function names
0413   functions_to_monitor=unique_wrapper(functions_to_monitor);
0414 
0415   % add functions to monitor list
0416   for i=1:length(functions_to_monitor)
0417     model.monitors.(functions_to_monitor{i})=[];
0418   end
0419 end
0420 
0421   % ----------------------------------
0422   % NESTED FUNCTIONS
0423   % ----------------------------------
0424   function add_keywords(src,dst,namespace)
0425     % NOTE: this needs to be coordinated with update_keywords() in dsSimulate()
0426     %   for parameters
0427     Nsrc=[src '_Npop'];
0428     Ndst=[dst '_Npop'];
0429 
0430     old={'Npre','N[1]','N_pre','Npost','N_post','N[0]','Npop','N_pop','tspike_pre','tspike_post','tspike'};
0431     new={Nsrc,Nsrc,Nsrc,Ndst,Ndst,Ndst,Ndst,Ndst,[src '_tspike'],[dst '_tspike'],[dst '_tspike']};
0432     for p=1:length(old)
0433       name_map(end+1,:)={old{p},new{p},namespace,'parameters'};
0434     end
0435 
0436     % for state variables
0437     new={};
0438     old={};
0439     src_excluded=~cellfun(@isempty,regexp(name_map(:,1),['pre' '$']));
0440     dst_excluded=~cellfun(@isempty,regexp(name_map(:,1),['post' '$']));
0441     excluded=src_excluded|dst_excluded;
0442 
0443     PopScope=[src '_'];
0444     var_idx=strcmp(PopScope,name_map(:,3)) & strcmp('state_variables',name_map(:,4)) & ~excluded;
0445     if any(var_idx)
0446       Xsrc_old_vars=name_map(var_idx,1);
0447       Xsrc_new_vars=name_map(var_idx,2);
0448       % default for IN is first Xsrc state var
0449       Xsrc=Xsrc_new_vars{1};
0450       old=cat(2,old,{'IN','Xpre','X_pre'});
0451       new=cat(2,new,{Xsrc,Xsrc,Xsrc});
0452     else
0453       Xsrc_old_vars=[];
0454       Xsrc_new_vars=[];
0455       Xsrc=[];
0456     end
0457 
0458     PopScope=[dst '_'];
0459     var_idx=strcmp(PopScope,name_map(:,3)) & strcmp('state_variables',name_map(:,4)) & ~excluded;
0460     if any(var_idx)
0461       Xdst_old_vars=name_map(var_idx,1);
0462       Xdst_new_vars=name_map(var_idx,2);
0463       % default for OUT and X is first Xdst state var
0464       Xdst=Xdst_new_vars{1};
0465       old=cat(2,old,{'OUT','X','Xpost','X_post'});
0466       new=cat(2,new,{Xdst,Xdst,Xdst,Xdst});
0467     else
0468       Xdst_old_vars=[];
0469       Xdst_new_vars=[];
0470       Xdst=[];
0471     end
0472 
0473     % add variants [var_pre,var_post,varpre,varpost]
0474     if ~isempty(Xsrc_old_vars)
0475       [Xsrc_old_vars,IA]=setdiff(Xsrc_old_vars,old);
0476       Xsrc_new_vars=Xsrc_new_vars(IA);
0477     end
0478 
0479     if ~isempty(Xdst_old_vars)
0480       [Xdst_old_vars,IA]=setdiff(Xdst_old_vars,old);
0481       Xdst_new_vars=Xdst_new_vars(IA);
0482     end
0483 
0484     for p=1:length(Xsrc_old_vars)
0485       old{end+1}=[Xsrc_old_vars{p} '_pre'];
0486       new{end+1}=Xsrc_new_vars{p};
0487       old{end+1}=[Xsrc_old_vars{p} 'pre'];
0488       new{end+1}=Xsrc_new_vars{p};
0489     end
0490 
0491     for p=1:length(Xdst_old_vars)
0492       old{end+1}=[Xdst_old_vars{p} '_post'];
0493       new{end+1}=Xdst_new_vars{p};
0494       old{end+1}=[Xdst_old_vars{p} 'post'];
0495       new{end+1}=Xdst_new_vars{p};
0496     end
0497 
0498     for p=1:length(old)
0499       name_map(end+1,:)={old{p},new{p},namespace,'state_variables'};
0500     end
0501   end
0502 
0503 %% 2.0 propagate namespaces through variable and function names
0504 %      i.e., to establish uniqueness of names by adding namespace/namespace prefixes)
0505 model.specification=specification;
0506 model = dsPropagateNamespaces(model,name_map, varargin{:});
0507 
0508 %% 3.0 expand population equations according to mechanism linkers
0509 % purpose: expand population equations according to linkers
0510 % - link populations.equations to mechanism sub-models
0511 % - link mechanism functions and state variables across mechanisms in a given population
0512 
0513 % store indices to all expressions and conditionals that are linked (this
0514 % is necessary for efficiently removing linker targets from expressions after linking)
0515 all_expression_inds=[];
0516 all_expression_targets={};
0517 all_conditionals_inds=[];
0518 all_conditionals_targets={};
0519 
0520 % add variables to linked expression if its a function without ()
0521 if ~isempty(model.functions) && ~isempty(model.linkers)
0522   function_names=fieldnames(model.functions);
0523   expressions={model.linkers.expression};
0524   [~,I,J]=intersect(function_names,expressions);
0525   for i=1:length(I)
0526     e=model.functions.(function_names{I(i)}); % function expression (eg,'@(x,y,z)x-(y-z)')
0527     v=regexp(e,'@(\([\w,]+\))','tokens','once'); % function input list (eg, '(x,y,z)')
0528     if ~isempty(v)
0529       model.linkers(J(i)).expression=[model.linkers(J(i)).expression v{1}];
0530     end
0531   end
0532 end
0533 
0534 % loop over linkers
0535 for i=1:length(model.linkers)
0536   % determine how to link
0537   operation=model.linkers(i).operation;
0538   oldstr=model.linkers(i).target;
0539   newstr=model.linkers(i).expression;
0540   switch operation % see dsClassifyEquation and dsParseModelEquations   % ('((\+=)|(-=)|(\*=)|(/=)|(=>))')
0541     case '+='
0542       operator='+';
0543     case '-='
0544       operator='-';
0545     case '*='
0546       operator='.*';
0547     case '/='
0548       operator='./';
0549     otherwise
0550       operator='+';
0551   end
0552   % determine what to link (ie, link across everything belonging to the linker population)
0553   % explicitly constrain to linker population
0554   expressions_in_pop=~cellfun(@isempty,regexp(name_map(:,3),['^' linker_pops{i}]));
0555 
0556   if ~isempty(model.conditionals)
0557     conditionals_in_pop=~cellfun(@isempty,regexp({model.conditionals.namespace},['^' linker_pops{i}]));
0558   end
0559 
0560   if ~isempty(model.linkers)
0561     linkers_in_pop=~cellfun(@isempty,regexp({model.linkers.namespace},['^' linker_pops{i}]));
0562   end
0563 
0564   % constrain to namespace
0565   names_in_namespace=cellfun(@(x,y)strncmp(y,x,length(y)),name_map(:,2),name_map(:,3));
0566 
0567   % get list of (functions,monitors,ODEs) belonging to the linker population
0568   eqn_types={'ODEs','monitors','functions'};%{'monitors','ODEs'};
0569   search_types={'state_variables','monitors','functions'};%{'monitors','state_variables'};
0570 
0571   % indices to expressions in the linker population with the correct search_types and namespace
0572   inds=find(expressions_in_pop&names_in_namespace&ismember(name_map(:,4),search_types));
0573 
0574   % eliminate duplicates (e.g., state_variables replacing OUT and X)
0575   [jnk,ia,ib]=unique_wrapper(name_map(inds,2),'stable');
0576   inds=inds(ia);
0577   all_expression_inds=[all_expression_inds inds'];
0578   all_expression_targets=cat(2,all_expression_targets,repmat({oldstr},[1 length(inds)]));
0579 
0580   % substitute link
0581   for j=1:length(inds)
0582     name=name_map{inds(j),2}; % name of variable as stored in model structure
0583     type=name_map{inds(j),4}; % search_types
0584     eqn_type=eqn_types{strcmp(type,search_types)}; % corresponding equation type
0585 
0586     % update expression with the current link
0587     if isfield(model.(eqn_type),name)
0588       % note: name will not be a field of eqn_type for special monitors
0589       % (e.g., monitor functions)
0590       model.(eqn_type).(name)=linker_strrep(model.(eqn_type).(name),oldstr,newstr,operator);
0591     end
0592   end
0593 
0594   if ~isempty(model.conditionals)
0595     fields={'condition','action','else'};
0596 
0597     % get list of conditionals belonging to the linker population
0598     inds=find(conditionals_in_pop);
0599     all_conditionals_inds=[all_conditionals_inds inds];
0600     all_conditionals_targets=cat(2,all_conditionals_targets,repmat({oldstr},[1 length(inds)]));
0601 
0602     % substitute link
0603     for j=1:length(inds)
0604       for field_index=1:length(fields)
0605         field=fields{field_index};
0606         model.conditionals(inds(j)).(field)=linker_strrep(model.conditionals(inds(j)).(field),oldstr,newstr,operator);
0607       end
0608     end
0609   end
0610 
0611   if ~isempty(model.linkers)
0612     inds=find(linkers_in_pop);
0613     for j=1:length(inds)
0614       model.linkers(inds(j)).expression=linker_strrep(model.linkers(inds(j)).expression,oldstr,newstr,operator);
0615     end
0616   end
0617 end
0618 
0619 if options.open_link_flag==0
0620   % remove target placeholders from expressions and conditionals
0621   for i=1:length(all_expression_inds)
0622     oldstr=all_expression_targets{i};
0623     newstr='';
0624     name=name_map{all_expression_inds(i),2};
0625     type=name_map{all_expression_inds(i),4};
0626     eqn_type=eqn_types{strcmp(type,search_types)};
0627     pattern = ['\)\.?[-\+\*/]' oldstr '\)']; % pattern accounts for all possible newstr defined for linking
0628     replace = [newstr '))'];
0629     if isfield(model.(eqn_type),name) && ischar(model.(eqn_type).(name))
0630         % NOTE: name will not be a field of eqn_type for special monitors
0631         % (e.g., monitor functions)
0632       model.(eqn_type).(name)=regexprep(model.(eqn_type).(name),pattern,replace);
0633     end
0634   end
0635 end
0636 
0637 if ~isempty(model.conditionals)
0638   for i=1:length(all_conditionals_inds)
0639     oldstr=all_conditionals_targets{i};
0640     newstr='';
0641     pattern = ['\)\.?[-\+\*/]' oldstr '\)']; % pattern accounts for all possible newstr defined for linking
0642     replace = [newstr '))'];
0643     for field_index=1:length(fields)
0644       field=fields{field_index};
0645       if model.conditionals(all_conditionals_inds(i)).(field)
0646         model.conditionals(all_conditionals_inds(i)).(field)=regexprep(model.conditionals(all_conditionals_inds(i)).(field),pattern,replace);
0647       end
0648     end
0649   end
0650 end
0651 
0652 % ------------------------------------------
0653 % NOTE on non-ideal implementation of 3.0: model.linkers does not contain enough
0654 % information to determine the population to which it belongs in all cases
0655 % (due to namespace format differences for population vs connection mechanisms & models
0656 % with one vs more populations). consequently, had to perform linking in this
0657 % function using info stored above while parsing the model; ideally, the
0658 % linking could occur independently of this function, informed by info in
0659 % model.linkers, and be packaged in its own external function LinkMechanisms().
0660 % ------------------------------------------
0661 
0662 %% 4.0 finalize
0663 
0664 % 4.1 sort .ODEs and .ICs wrt .state_variables
0665 if ~isempty(model.ODEs)
0666   model.ODEs = orderfields(model.ODEs,model.state_variables);
0667   model.ICs = orderfields(model.ICs,model.state_variables);
0668 end
0669 
0670 % 4.2 convert to numeric parameters
0671 c = struct2cell(model.parameters);
0672 
0673 % get index of strings
0674 idx1=find(cellfun(@ischar,c));
0675 
0676 % which strings contain numeric values?
0677 idx2=find(cellfun(@isempty,regexp(c(idx1),'[a-z_A-Z]')) | ~cellfun(@isempty,regexp(c(idx1),'^\s*\[*\s*\+?inf\s*\]*\s*$','ignorecase')));
0678 
0679 % convert those strings which contain numeric values
0680 c(idx1(idx2)) = cellfun(@eval,c(idx1(idx2)),'uni',0);
0681 f = fieldnames(model.parameters);
0682 model.parameters = cell2struct(c,f,1);
0683 
0684 % 4.3 store original specification
0685 model.specification = specification; % store specification to enable modifications to be applied later
0686 model.namespaces = name_map; % store name_map for transparency
0687 
0688 model=dsCheckModel(model, varargin{:});
0689 
0690 %% auto_gen_test_data_flag argout
0691 if options.auto_gen_test_data_flag
0692   argout = {model, name_map}; % specific to this function
0693 
0694   dsUnitSaveAutoGenTestData(argin, argout);
0695 end
0696 
0697 end % main function
0698 
0699 
0700 %% SUBFUNCTIONS
0701 function str=linker_strrep(str,oldstr,newstr,operator)
0702   if isempty(str)
0703     return;
0704   end
0705   % if inserting one word (e.g., a state variable), just replace it
0706   % WARNING: could cause problems in future if there is an additive
0707   % substitution of different state variables into the same place followed
0708   % by non-additive operations (e.g., @cai+=cai1 and @cai+=cai2 into
0709   % v'=f(v)*cai where cai1 & cai2 are defined in mechanisms for the same v;
0710   % workaround: insert into v'=f(v)*(cai)).
0711 
0712   % check if anything besides a single variable:
0713   if isempty(regexp(newstr,'[^a-z_A-Z\d]+','once'))
0714     str=dsStrrep(str,oldstr,newstr);
0715   else
0716     % otherwise do substitution with operator and parenthesis
0717     pat=['([^\w]+)' oldstr '([^\w]+)']; % in the middle
0718     rep=['$1((' newstr ')' operator oldstr ')$2'];
0719     str=regexprep(str,pat,rep);
0720     pat=['([^\w]+)' oldstr '$'];        % at the end
0721     rep=['$1((' newstr ')' operator oldstr ')'];
0722     str=regexprep(str,pat,rep);
0723     pat=['^' oldstr '([^\w]+)'];        % at the beginning
0724     rep=['((' newstr ')' operator oldstr ')$1'];
0725     str=regexprep(str,pat,rep);
0726     pat=['^' oldstr '$'];               % all there is
0727     rep=['((' newstr ')' operator oldstr ')'];
0728     str=regexprep(str,pat,rep);
0729   end
0730 end

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