function [model,name_map]=GenerateModel(specification,varargin)
%% [model,name_map]=GenerateModel(specification,'option',value,...)
% Purpose: parse DynaSim specification and organize model data in DynaSim model structure
% Inputs:
% specification: one of --
% - DynaSim specification structure (see below and CheckSpecification 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 ApplyModifications for more details).
% - 'open_link_flag' : whether to leave linker identifiers in place (default: 0)
% Outputs:
% model - DynaSim model structure (see CheckModel 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 CheckModel)
% .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 CheckModel)
% .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 CheckModel)
% 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 CheckSpecification 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 CheckSpecification)
% .mechanism_list (default: []): cell array listing mechanisms (see NOTE 2 in CheckSpecification)
% .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 source 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.
%
% Example 0:
% model=GenerateModel('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=GenerateModel(eqns)
%
% Example 2: Leaky integrate-and-fire neuron
% model=GenerateModel('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=GenerateModel('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=GenerateModel(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=GenerateModel(specification)
% or:
% specification.populations(1).equations='dv/dt=@M+sin(2*pi); {iNa,iK}@M; v(0)=-65';
% model=GenerateModel(specification)
%
% Example 6: directly incorporating mechanism models from online repositories:
% model=GenerateModel('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: CheckSpecification, CheckModel, ParseModelEquations, SimulateModel
% Check inputs
% ------------------------------------------------------
if nargin==0
% use default model
specification=[];
specification.populations(1).equations='dv/dt=10+@current/Cm; Cm=1; v(0)=-65';
specification.populations(1).mechanism_list={'iNa','iK'};
specification.populations(1).parameters={'Cm',1};
specification.connections(1).mechanism_list={'iGABAa'};
varargin={'modifications',[]};
end
% ------------------------------------------------------
options=CheckOptions(varargin,{...
'modifications',[],[],...
'open_link_flag',0,{0,1},...
},false);
% check if a model
if isfield(specification,'state_variables')
% do nothing
model=specification;
return;
% todo: consider the following --
% if isfield(specification,'specification')
% % regenerate from specification
% specification=specification.specification;
% end
end
% standardize specification
specification=CheckSpecification(specification); % standardize & auto-populate as needed
% Apply modifications to specification before generating model
if ~isempty(options.modifications)
specification=ApplyModifications(specification,options.modifications);
end
% specification metadata:
npops=length(specification.populations); % number of populations
ncons=length(specification.connections); % number of connections
%{
% Dev notes on improving implementation:
% Ideally (1.0)-(3.0) could be packaged into external functions and run as:
% -------------------------------------------------------------------------
%% 1.0 load sub-models, assign namespaces, and combine across all equations and mechanisms in specification
% [model,name_map]=LoadModelSet(specification) % bug: disrupted subsequent namespace propagation (without raising an error)
%% 2.0 propagate namespaces through variable and function names
% model = PropagateNamespaces(model,name_map); % this works
%% 3.0 expand population equations according to mechanism linkers
% model = LinkMechanisms(model,name_map); % problem: unable to identify linker population from model.linkers; see notes below (3.0) for more details
% -------------------------------------------------------------------------
%}
% support full modularization of mechanisms
% (eg, dv/dt=@M; {Na,K}@M w/ Na.mech: @current += I(IN,m,h)).
% approach taken below:
% - add support for dv/dt=@M; {Na@M,K@M}
% have GenerateModel split mech_name on '@' and replace first
% linker in mech (e.g., @current) by what follows '@' (e.g., @M)
% - then have CheckSpecification convert {Na,K}@M into {Na@M,K@M}
%% 1.0 load sub-models, assign namespaces, and combine across all equations and mechanisms in specification
model.parameters={};
model.fixed_variables=[];
model.functions=[];
model.monitors=[];
model.state_variables={};
model.ODEs=[];
model.ICs=[];
model.conditionals=[];
model.linkers=[];
model.comments={};
name_map={}; % {name, namespace_name, namespace, type}, used for namespacing
linker_pops={}; % list of populations associated with mechanism linkers
% 1.1 load and combine population sub-models from population equations and mechanisms
for i=1:npops
% does the population model already exist?
if ~isempty(specification.populations(i).model)
tmpmodel=specification.populations(i).model; % get model structure
tmpname=tmpmodel.specification.populations.name; % assumes one population sub-model
% adjust the name if necessary
if ~strcmp(specification.populations(i).name,tmpname)
% use the name in the specification
tmpmodel=ApplyModifications(tmpmodel,{tmpname,'name',specification.populations(i).name});
elseif strcmp(tmpname,'pop1') % if default name
% use default name for this population index
tmpmodel=ApplyModifications(tmpmodel,{tmpname,'name',sprintf('pop%g',i)});
end
tmpmodel.linkers=[]; % remove old linkers from original model construction
model=CombineModels(model,tmpmodel);
name_map=cat(1,name_map,tmpmodel.namespaces);
continue;
end
% construct new population model
PopScope=specification.populations(i).name;
% note: ParseModelEquations adds a '_' suffix to the namespace; therefore,
% a '_' suffix is added to PopScope when used below for consistency of
% namespaces/namespaces. (this could be cleaned up by adding '_' to PopScope
% here, removing it below, and removing the additional '_' from
% ParseModelEquations).
% 1.1.1 parse population equations
equations=specification.populations(i).equations;
parameters=specification.populations(i).parameters;
nmechs=length(specification.populations(i).mechanism_list);
% parse population equations
if ~isempty(equations)
[tmpmodel,tmpmap]=ImportModel(equations,'namespace',PopScope,'ic_pop',specification.populations(i).name,'user_parameters',parameters);
model=CombineModels(model,tmpmodel);
name_map=cat(1,name_map,tmpmap);
end
% 1.1.2 parse population mechanisms
for j=1:nmechs
mechanism_=specification.populations(i).mechanism_list{j};
% support separation of linker names in pop equations vs mechanisms
mechanism_=regexp(mechanism_,'@','split');
mechanism=mechanism_{1};
if numel(mechanism_)>1, new_linker=mechanism_{2}; else new_linker=[]; end
% set mechanism namespace
if any(mechanism==':')
% exclude host name from namespace
tmp=regexp(mechanism,':','split');
MechScope=[specification.populations(i).name '_' tmp{2}];
else
% extract mechanism file name without path
[~,MechID]=fileparts(mechanism);
MechScope=[specification.populations(i).name '_' MechID];
end
% parse mechanism equations
[tmpmodel,tmpmap]=ImportModel(mechanism,'namespace',MechScope,'ic_pop',specification.populations(i).name,'user_parameters',parameters);
% replace 1st linker name by the one in specification
if ~isempty(new_linker) && ~isempty(tmpmodel.linkers)
% first try to find 1st linker target starting with @
links_at=find(~cellfun(@isempty,regexp({tmpmodel.linkers.target},'^@','once')));
if ~isempty(links_at)
% use first link with target prepended by '@'
link_ind=links_at(1);
else
% use first link
link_ind=1;
end
tmpmodel.linkers(link_ind).target=['@' new_linker];
end
% combine sub-model with other sub-models
model=CombineModels(model,tmpmodel);
name_map=cat(1,name_map,tmpmap);
linker_pops=cat(2,linker_pops,repmat({specification.populations(i).name},[1 length(tmpmodel.linkers)]));
end
pop=specification.populations(i).name;
% add reserved keywords (parameters and state variables) to name_map
add_keywords(pop,pop,[PopScope '_']);
model.parameters.([pop '_Npop'])=num2str(specification.populations(i).size);
end
% 1.2 load and combine sub-models from connection mechanisms
for i=1:ncons
% parse connection mechanisms
source=specification.connections(i).source;
target=specification.connections(i).target;
parameters=specification.connections(i).parameters;
ConScope=[target '_' source '_'];
% note: in contrast to PopScope above, ConScope is never passed to
% ParseModelEquations; thus the '_' should be added here for consistency
% with mechanism namespaces (which are modified by ParseModelEquations).
for j=1:length(specification.connections(i).mechanism_list)
mechanism_=specification.connections(i).mechanism_list{j};
% support separation of linker names in pop equations vs mechanisms
mechanism_=regexp(mechanism_,'@','split');
mechanism=mechanism_{1};
if numel(mechanism_)>1, new_linker=mechanism_{2}; else new_linker=[]; end
% extract mechanism file name without path
[~,MechID]=fileparts(mechanism);
MechScope=[target '_' source '_' MechID];
% note: must use target_source_mechanism for connection mechanisms
% to distinguish their parent namespaces from those of population mechanisms
% see: GetParentNamespace
% parse model equations
[tmpmodel,tmpmap]=ImportModel(mechanism,'namespace',MechScope,'ic_pop',source,'user_parameters',parameters);
% replace 1st linker name by the one in specification
if ~isempty(new_linker) && ~isempty(tmpmodel.linkers)
tmpmodel.linkers(1).target=['@' new_linker];
end
model=CombineModels(model,tmpmodel);
name_map=cat(1,name_map,tmpmap);
% link this mechanism to the target population
linker_pops=cat(2,linker_pops,repmat(target,[1 length(tmpmodel.linkers)]));
% edit names of connection monitors specified in population equations
% todo: consider design changes to avoid specifying connection monitors
% in population equations; this is an undesirable hack:
% eg, convert E_iGABAa_functions -> I_E_iGABAa_functions
if ~isempty(model.monitors)
% get indices to all model.monitors that have incorrect connection namespace
con_mon_to_update=find(~cellfun(@isempty,regexp(fieldnames(model.monitors),['^' target '_' mechanism])));
if any(con_mon_to_update)
% get list of current model.monitors
monitor_names=fieldnames(model.monitors);
for m=1:length(con_mon_to_update)
% get name of monitor with incorrect connection namespace
old=monitor_names{con_mon_to_update(m)};
% get name of monitor with correct connection namespace
new=strrep(old,[target '_' mechanism '_'],[MechScope '_']);
% add new monitor with correct namespace
model.monitors.(new)=model.monitors.(old);
% remove old monitor with incorrect namespace
model.monitors=rmfield(model.monitors,old);
end
end
end
end
% add reserved keywords (parameters and state variables) to name_map
add_keywords(source,target,ConScope);
end
% check for monitoring functions (e.g., 'monitor functions' or 'monitor Na.functions')
if ~isempty(model.monitors)
% get list of functions
if ~isempty(model.functions)
function_names=fieldnames(model.functions);
else
function_names={};
end
% get list of monitor names
monitor_names=fieldnames(model.monitors);
% get indices to monitors with names ending in _functions
function_monitor_index=find(~cellfun(@isempty,regexp(monitor_names,'_functions$','once')));
% create list of functions with namespaces matching monitors ending in _functions
functions_to_monitor={};
for i=1:length(function_monitor_index)
% get namespace of functions to monitor
monitor_name=monitor_names{function_monitor_index(i)};
monitor_namespace=regexp(monitor_name,'(.*)_functions$','tokens','once');
monitor_namespace=monitor_namespace{1};
% get list of functions with matching namespace
function_index=find(~cellfun(@isempty,regexp(function_names,['^' monitor_namespace],'once')));
% add functions to list
functions_to_monitor=cat(1,functions_to_monitor,function_names(function_index));
% remove "function" monitor name
model.monitors=rmfield(model.monitors,monitor_name);
end
% eliminate duplicate function names
functions_to_monitor=unique(functions_to_monitor);
% add functions to monitor list
for i=1:length(functions_to_monitor)
model.monitors.(functions_to_monitor{i})=[];
end
end
% ----------------------------------
% NESTED FUNCTIONS
% ----------------------------------
function add_keywords(src,dst,namespace)
% note: this needs to be coordinated with update_keywords() in SimulateModel()
% for parameters
Nsrc=[src '_Npop'];
Ndst=[dst '_Npop'];
old={'Npre','N[1]','N_pre','Npost','N_post','N[0]','Npop','N_pop'};
new={Nsrc,Nsrc,Nsrc,Ndst,Ndst,Ndst,Ndst,Ndst};
for p=1:length(old)
name_map(end+1,:)={old{p},new{p},namespace,'parameters'};
end
% for state variables
new={};
old={};
src_excluded=~cellfun(@isempty,regexp(name_map(:,1),['pre' '$']));
dst_excluded=~cellfun(@isempty,regexp(name_map(:,1),['post' '$']));
excluded=src_excluded|dst_excluded;
PopScope=[src '_'];
var_idx=strcmp(PopScope,name_map(:,3)) & strcmp('state_variables',name_map(:,4)) & ~excluded;
if any(var_idx)
Xsrc_old_vars=name_map(var_idx,1);
Xsrc_new_vars=name_map(var_idx,2);
% default for IN is first Xsrc state var
Xsrc=Xsrc_new_vars{1};
old=cat(2,old,{'IN','Xpre','X_pre'});
new=cat(2,new,{Xsrc,Xsrc,Xsrc});
else
Xsrc_old_vars=[];
Xsrc_new_vars=[];
Xsrc=[];
end
PopScope=[dst '_'];
var_idx=strcmp(PopScope,name_map(:,3)) & strcmp('state_variables',name_map(:,4)) & ~excluded;
if any(var_idx)
Xdst_old_vars=name_map(var_idx,1);
Xdst_new_vars=name_map(var_idx,2);
% default for OUT and X is first Xdst state var
Xdst=Xdst_new_vars{1};
old=cat(2,old,{'OUT','X','Xpost','X_post'});
new=cat(2,new,{Xdst,Xdst,Xdst,Xdst});
else
Xdst_old_vars=[];
Xdst_new_vars=[];
Xdst=[];
end
% add variants [var_pre,var_post,varpre,varpost]
if ~isempty(Xsrc_old_vars)
[Xsrc_old_vars,IA]=setdiff(Xsrc_old_vars,old);
Xsrc_new_vars=Xsrc_new_vars(IA);
end
if ~isempty(Xdst_old_vars)
[Xdst_old_vars,IA]=setdiff(Xdst_old_vars,old);
Xdst_new_vars=Xdst_new_vars(IA);
end
for p=1:length(Xsrc_old_vars)
old{end+1}=[Xsrc_old_vars{p} '_pre'];
new{end+1}=Xsrc_new_vars{p};
old{end+1}=[Xsrc_old_vars{p} 'pre'];
new{end+1}=Xsrc_new_vars{p};
end
for p=1:length(Xdst_old_vars)
old{end+1}=[Xdst_old_vars{p} '_post'];
new{end+1}=Xdst_new_vars{p};
old{end+1}=[Xdst_old_vars{p} 'post'];
new{end+1}=Xdst_new_vars{p};
end
for p=1:length(old)
name_map(end+1,:)={old{p},new{p},namespace,'state_variables'};
end
end
%% 2.0 propagate namespaces through variable and function names
% i.e., to establish uniqueness of names by adding namespace/namespace prefixes)
model = PropagateNamespaces(model,name_map);
%% 3.0 expand population equations according to mechanism linkers
% purpose: expand population equations according to linkers
% - link populations.equations to mechanism sub-models
% - link mechanism functions and state variables across mechanisms in a given population
% store indices to all expressions and conditionals that are linked (this
% is necessary for efficiently removing linker targets from expressions after linking)
all_expression_inds=[];
all_expression_targets={};
all_conditionals_inds=[];
all_conditionals_targets={};
% add variables to linked expression if its a function without ()
if ~isempty(model.functions) && ~isempty(model.linkers)
function_names=fieldnames(model.functions);
expressions={model.linkers.expression};
[~,I,J]=intersect(function_names,expressions);
for i=1:length(I)
e=model.functions.(function_names{J(i)}); % function expression (eg,'@(x,y,z)x-(y-z)')
v=regexp(e,'@(\([\w,]+\))','tokens','once'); % function input list (eg, '(x,y,z)')
if ~isempty(v)
model.linkers(I(i)).expression=[model.linkers(I(i)).expression v{1}];
end
end
end
% loop over linkers
for i=1:length(model.linkers)
% determine how to link
operation=model.linkers(i).operation;
oldstr=model.linkers(i).target;
newstr=model.linkers(i).expression;
switch operation % see ClassifyEquation and ParseModelEquations % ('((\+=)|(-=)|(\*=)|(/=)|(=>))')
case '+='
operator='+';
case '-='
operator='-';
case '*='
operator='.*';
case '/='
operator='./';
otherwise
operator='+';
end
% determine what to link (ie, link across everything belonging to the linker population)
% explicitly constrain to linker population
expressions_in_pop=~cellfun(@isempty,regexp(name_map(:,3),['^' linker_pops{i}]));
if ~isempty(model.conditionals)
conditionals_in_pop=~cellfun(@isempty,regexp({model.conditionals.namespace},['^' linker_pops{i}]));
end
if ~isempty(model.linkers)
linkers_in_pop=~cellfun(@isempty,regexp({model.linkers.namespace},['^' linker_pops{i}]));
end
% constrain to namespace
names_in_namespace=cellfun(@(x,y)strncmp(y,x,length(y)),name_map(:,2),name_map(:,3));
% get list of (functions,monitors,ODEs) belonging to the linker population
eqn_types={'ODEs','monitors','functions'};%{'monitors','ODEs'};
search_types={'state_variables','monitors','functions'};%{'monitors','state_variables'};
% indices to expressions in the linker population with the correct search_types and namespace
inds=find(expressions_in_pop&names_in_namespace&ismember(name_map(:,4),search_types));
% eliminate duplicates (e.g., state_variables replacing OUT and X)
[jnk,ia,ib]=unique(name_map(inds,2),'stable');
inds=inds(ia);
all_expression_inds=[all_expression_inds inds'];
all_expression_targets=cat(2,all_expression_targets,repmat({oldstr},[1 length(inds)]));
% substitute link
for j=1:length(inds)
name=name_map{inds(j),2}; % name of variable as stored in model structure
type=name_map{inds(j),4}; % search_types
eqn_type=eqn_types{strcmp(type,search_types)}; % corresponding equation type
% update expression with the current link
if isfield(model.(eqn_type),name)
% note: name will not be a field of eqn_type for special monitors
% (e.g., monitor functions)
model.(eqn_type).(name)=linker_strrep(model.(eqn_type).(name),oldstr,newstr,operator);
end
end
if ~isempty(model.conditionals)
fields={'condition','action','else'};
% get list of conditionals belonging to the linker population
inds=find(conditionals_in_pop);
all_conditionals_inds=[all_conditionals_inds inds];
all_conditionals_targets=cat(2,all_conditionals_targets,repmat({oldstr},[1 length(inds)]));
% substitute link
for j=1:length(inds)
for field_index=1:length(fields)
field=fields{field_index};
model.conditionals(inds(j)).(field)=linker_strrep(model.conditionals(inds(j)).(field),oldstr,newstr,operator);
end
end
end
if ~isempty(model.linkers)
inds=find(linkers_in_pop);
for j=1:length(inds)
model.linkers(inds(j)).expression=linker_strrep(model.linkers(inds(j)).expression,oldstr,newstr,operator);
end
end
end
if options.open_link_flag==0
% remove target placeholders from expressions and conditionals
for i=1:length(all_expression_inds)
oldstr=all_expression_targets{i};
newstr='';
name=name_map{all_expression_inds(i),2};
type=name_map{all_expression_inds(i),4};
eqn_type=eqn_types{strcmp(type,search_types)};
pattern = ['\)\.?[-\+\*/]' oldstr '\)']; % pattern accounts for all possible newstr defined for linking
replace = [newstr '))'];
if isfield(model.(eqn_type),name) && ischar(model.(eqn_type).(name))
% note: name will not be a field of eqn_type for special monitors
% (e.g., monitor functions)
model.(eqn_type).(name)=regexprep(model.(eqn_type).(name),pattern,replace);
end
end
end
if ~isempty(model.conditionals)
for i=1:length(all_conditionals_inds)
oldstr=all_conditionals_targets{i};
newstr='';
pattern = ['\)\.?[-\+\*/]' oldstr '\)']; % pattern accounts for all possible newstr defined for linking
replace = [newstr '))'];
for field_index=1:length(fields)
field=fields{field_index};
if model.conditionals(all_conditionals_inds(i)).(field)
model.conditionals(all_conditionals_inds(i)).(field)=regexprep(model.conditionals(all_conditionals_inds(i)).(field),pattern,replace);
end
end
end
end
% ------------------------------------------
% note on non-ideal implementation of 3.0: model.linkers does not contain enough
% information to determine the population to which it belongs in all cases
% (due to namespace format differences for population vs connection mechanisms & models
% with one vs more populations). consequently, had to perform linking in this
% function using info stored above while parsing the model; ideally, the
% linking could occur independently of this function, informed by info in
% model.linkers, and be packaged in its own external function LinkMechanisms().
% ------------------------------------------
%% 4.0 finalize
% 4.1 sort .ODEs and .ICs wrt .state_variables
model.ODEs = orderfields(model.ODEs,model.state_variables);
model.ICs = orderfields(model.ICs,model.state_variables);
% 4.2 convert to numeric parameters
c = struct2cell(model.parameters);
% get index of strings
idx1=find(cellfun(@ischar,c));
% which strings contain numeric values?
idx2=find(cellfun(@isempty,regexp(c(idx1),'[a-z_A-Z]')) | ~cellfun(@isempty,regexp(c(idx1),'^\s*\[*\s*\+?inf\s*\]*\s*$','ignorecase')));
% convert those strings which contain numeric values
c(idx1(idx2)) = cellfun(@eval,c(idx1(idx2)),'uni',0);
%idx=cellfun(@isempty,regexp(c,'[a-z_A-Z]')) | ~cellfun(@isempty,regexp(c,'^\s*\[*\s*inf\s*\]*\s*$','ignorecase'));
%c(idx) = cellfun(@eval,c(idx),'uni',0);
f = fieldnames(model.parameters);
model.parameters = cell2struct(c,f,1);
% 4.3 store original specification
model.specification = specification; % store specification to enable modifications to be applied later
model.namespaces = name_map; % store name_map for transparency
model=CheckModel(model);
end
% SUBFUNCTIONS
function str=linker_strrep(str,oldstr,newstr,operator)
if isempty(str)
return;
end
% if inserting one word (e.g., a state variable), just replace it
% warning: could cause problems in future if there is an additive
% substitution of different state variables into the same place followed
% by non-additive operations (e.g., @cai+=cai1 and @cai+=cai2 into
% v'=f(v)*cai where cai1 & cai2 are defined in mechanisms for the same v;
% workaround: insert into v'=f(v)*(cai)).
% check if anything besides a single variable:
if isempty(regexp(newstr,'[^a-z_A-Z\d]+','once'))
str=dynasim_strrep(str,oldstr,newstr);
else
% otherwise do substitution with operator and parenthesis
pat=['([^\w]+)' oldstr '([^\w]+)']; % in the middle
rep=['$1((' newstr ')' operator oldstr ')$2'];
str=regexprep(str,pat,rep);
pat=['([^\w]+)' oldstr '$']; % at the end
rep=['$1((' newstr ')' operator oldstr ')'];
str=regexprep(str,pat,rep);
pat=['^' oldstr '([^\w]+)']; % at the beginning
rep=['((' newstr ')' operator oldstr ')$1'];
str=regexprep(str,pat,rep);
pat=['^' oldstr '$']; % all there is
rep=['((' newstr ')' operator oldstr ')'];
str=regexprep(str,pat,rep);
end
end