function model = dsPropagateNamespaces(model,map, varargin)
%PROPAGATENAMESPACES - namespace-establishing namespace substitutions.
%
% Usage:
% model = dsPropagateNamespaces(model,name_map)
%
% Inputs:
% - model: DynaSim model structure (see dsGenerateModel)
% - 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').
%
% Outputs:
% - model: DynaSim model structure with namespace added as namespace-delineating prefix
%
% Example 1: TODO
%
% See also: dsGenerateModel, dsPropagateFunctions, dsParseModelEquations, dsGetParentNamespace
%
% Author: Jason Sherfey, PhD <jssherfey@gmail.com>
% Copyright (C) 2016 Jason Sherfey, Boston University, USA
%% auto_gen_test_data_flag argin
options = dsCheckOptions(varargin,{'auto_gen_test_data_flag',0,{0,1}},false);
if options.auto_gen_test_data_flag
varargs = varargin;
varargs{find(strcmp(varargs, 'auto_gen_test_data_flag'))+1} = 0;
varargs(end+1:end+2) = {'unit_test_flag',1};
argin = [{model}, {map}, varargs]; % specific to this function
end
% Check model
model=dsCheckModel(model, varargin{:});
% Check map
if ~iscell(map) || size(map,2)~=4
error('map must be a cell array with four columns for (name, namespace_name, namespace, type)');
end
names_in_namespace=cellfun(@(x,y)strncmp(y,x,length(y)),map(:,2),map(:,3));
[name_,name__]=dsGetNamespaces(model);
% Purpose: add double underscores between model objects (i.e., separate
% populations and mechanisms in namespace). This enables retrieving model
% object names from the namespace in dsGetParentNamespace for segregating
% model elements in dsPropagateNamespaces. This is necessary if object
% names contain underscores. Limitation: object names with double
% underscores are not permitted.
% Note: this approach to tracking objects with namespaces was chosen
% because of its efficiency given code generation based on string
% substitution.
% namespace propagation pattern:
allowed_insert_types.fixed_variables=...
{'parameters','fixed_variables','reserved'}; % into fixed_variables
allowed_insert_types.functions=...
{'parameters','fixed_variables','functions','state_variables','reserved'}; % into functions
allowed_insert_types.monitors=...
{'parameters','fixed_variables','functions','state_variables','reserved'}; % into monitors
allowed_insert_types.ODEs=...
{'parameters','fixed_variables','functions','state_variables','reserved'}; % into ODEs
allowed_insert_types.ICs=...
{'parameters','fixed_variables','functions','state_variables','reserved'}; % into ICs
allowed_insert_types.linkers=...
{'parameters','fixed_variables','functions','state_variables','reserved'};
allowed_insert_types.conditionals=...
{'parameters','fixed_variables','functions','state_variables','reserved'};
%% 1.0 Propagate through structure arrays (linkers, conditionals)
% 1.1 linkers (expression)
if ~isempty(model.linkers)
namespaces={model.linkers.namespace};
expressions=propagate_namespaces({model.linkers.expression},namespaces,map,allowed_insert_types.linkers);
[model.linkers(1:length(model.linkers)).expression]=deal(expressions{:});
end
% 1.2 conditionals (condition, action, else)
if ~isempty(model.conditionals)
namespaces={model.conditionals.namespace};
target_types={'condition','action','else'};
for type_index=1:length(target_types)
type=target_types{type_index};
tmp=propagate_namespaces({model.conditionals.(type)},namespaces,map,allowed_insert_types.conditionals);
[model.conditionals(1:length(model.conditionals)).(type)]=deal(tmp{:});
end
end
%% 2.0 Propagate through sub-structures
target_types={'fixed_variables','functions','monitors','ODEs','ICs'};
% loop over types of model data
for type_index = 1:length(target_types)
type=target_types{type_index};%'fixed_variables';
% info for this type
s=model.(type);
if isstruct(s)
fields=fieldnames(s); % namespaced-names of this type (ie, [namespace_name])
expressions=struct2cell(s); % raw-expressions of this type (ie, without namespace prefixes)
namespaces={};
for i=1:length(expressions)
idx=strcmp(fields{i},map(:,2));
if numel(find(idx))>1
% constrain to namespace-conserving entries
tmp=map(idx&names_in_namespace,3);
% use the lowest level namespace (i.e., longest namespace name)
l=cellfun(@length,tmp);
tmp=tmp{l==max(l)};
else
tmp=map{idx,3};
end
namespaces{end+1}=tmp;
end
% update expressions for names of this type
expressions=propagate_namespaces(expressions,namespaces,map,allowed_insert_types.(type));
% update model with expressions including namespaces
model.(type)=cell2struct(expressions,fields,1);
end
end
%% NESTED FUNCTIONS
% function expressions=propagate_namespaces(expressions,names_full,map,insert_types)
function expressions=propagate_namespaces(expressions,namespaces,map,insert_types)
% loop over and update expressions for names of this type
for i=1:length(expressions)
if isempty(expressions{i})
continue;
end
% get namespace for this expression
this_namespace=namespaces{i};
% convert to double underscore version for segregating objects
this_namespace__ = name__{strcmp(this_namespace,name_)};
% find parent namespaces (by segregating model objects delimited by underscores)
parent_namespace = dsGetParentNamespace(this_namespace__, varargin{:});
% find where this and parent namespaces are in map array
insert_type_constraint = ismember(map(:,4),insert_types);
this_namespace_map_inds = find(strcmp(this_namespace,map(:,3)) & insert_type_constraint);
parent_namespace_map_inds = find(strcmp(parent_namespace,map(:,3)) & insert_type_constraint);
% get list of words in this expression
words=unique(regexp(expressions{i},'[a-zA-Z]+\w*','match'));
% loop over words
for j=1:length(words)
% search for words in parent namespace of map.names
if any(strcmp(words{j},map(parent_namespace_map_inds,1))) % search parent namespace
% word found in parent namespace of map
ind=parent_namespace_map_inds(strcmp(words{j},map(parent_namespace_map_inds,1)));
new_word=map{ind,2};
%if IC, need to take just first time index
if exist('type','var') && strcmp(type, 'ICs') && strcmp(words{j}, 'X')
new_word = [new_word '_last']; % HACK
end
% TODO: move this to dsWriteDynaSimSolver()
% replace found word in expression by map(names_bar|parent_namespace)
expressions{i}=dsStrrep(expressions{i},words{j},new_word, '', '', varargin{:});
% check whether new word is defined in model
% NOTE: this is necessary to account for namespace differences between
% user-supplied population parameters that should replace default mechanism-level parameters
new_word_type=map{ind,4};
% %{
if ~isfield(model.(new_word_type),new_word)
% if not, define it from (word without namespace/namespace)
if isfield(model.(new_word_type),words{j})
model.(new_word_type).(new_word)=model.(new_word_type).(words{j});
model.(new_word_type) = rmfield(model.(new_word_type),words{j});
else
tmpi=find(strcmp(words{j},map(:,1))&strcmp(new_word_type,map(:,4))&strcmp(this_namespace,map(:,3)));
if ~isempty(tmpi)
old_field = map{tmpi,2};
if ~isempty(tmpi) && isfield(model.(new_word_type),old_field)
model.(new_word_type).(new_word)=model.(new_word_type).(old_field);
%model.(new_word_type) = rmfield(model.(new_word_type),old_field);
end
end
end
end
% %}
elseif any(strcmp(words{j},map(this_namespace_map_inds,1))) % search this namespace
% word found in this namespace of map
ind=this_namespace_map_inds(strcmp(words{j},map(this_namespace_map_inds,1)));
new_word=map{ind,2};
% replace found word in expression by map(names_bar|this_namespace)
expressions{i}=dsStrrep(expressions{i},words{j},new_word, '', '', varargin{:});
end
end
end
end
%% auto_gen_test_data_flag argout
if options.auto_gen_test_data_flag
argout = {model}; % specific to this function
dsUnitSaveAutoGenTestData(argin, argout);
end
end
% SUBFUNCTIONS
function parent = dsGetParentNamespace(namespace)
%GETPARENTNAMESPACE - determine parent namespace from namespace specified in namespace
% Usage:
% parent = dsGetParentNamespace(namespace)
% Input:
% - namespace: current namespace of object
% Output:
% - parent: parent namespace containing the current namespace
% Examples:
% parent=dsGetParentNamespace('pop')
% parent=dsGetParentNamespace('pop__mech')
% parent=dsGetParentNamespace('pop__pop')
% parent=dsGetParentNamespace('pop__pop__mech')
% parent=dsGetParentNamespace('mech')
% parent=dsGetParentNamespace('')
if isempty(namespace) && isnumeric(namespace)
namespace='';
end
if ~isempty(namespace) && namespace(end)=='_'
namespace=namespace(1:end-1);
end
if ~isempty(namespace)
parts=regexp(namespace,'__','split');
else
parts=[];
end
switch length(parts)
case 0 % ''
parent='global';
case 1 % pop or mech
parent='';
case 2
if isequal(parts{1},parts{2}) % pop_pop
parent='global';
else % pop_mech
parent=[parts{1} '_'];
end
case 3 % pop_pop_mech
parent=[parts{1} '_' parts{2} '_'];
otherwise % a_b_c_d_...
parent='';
for i=1:length(parts)-1
parent=[parent parts{i} '_'];
end
end
end