function [estimate] = ft_inverse_dics(sourcemodel, sens, headmodel, dat, C, varargin)

% FT_INVERSE_DICS scans on pre-defined dipole locations with a single dipole
% and returns the beamformer spatial filter output for a dipole on every
% location.
%
% Use as
%   [estimate] = ft_inverse_dics(sourcemodel, sens, headmodel, dat, cov, ...)
% where
%   sourcemodel is the input source model, see FT_PREPARE_SOURCEMODEL
%   sens        is the gradiometer or electrode definition, see FT_DATATYPE_SENS
%   headmodel   is the volume conductor definition, see FT_PREPARE_HEADMODEL
%   dat         is the data matrix with the ERP or ERF
%   cov         is the data covariance or cross-spectral density matrix
% and
%   estimate    contains the estimated source parameters
%
% Additional input arguments should be specified as key-value pairs and can include
%   'Pr'               = power of the external reference channel
%   'Cr'               = cross spectral density between all data channels and the external reference channel
%   'refdip'           = location of dipole with which coherence is computed
%   'powmethod'        = can be 'trace' or 'lambda1'
%   'feedback'         = can be 'none', 'gui', 'dial', 'textbar', 'text', 'textcr', 'textnl' (default = 'text')
%   'fixedori'         = use fixed or free orientation,                 can be 'yes' or 'no'
%   'projectnoise'     = project noise estimate through filter,         can be 'yes' or 'no'
%   'realfilter'       = construct a real-valued filter,                can be 'yes' or 'no'
%   'keepfilter'       = remember the beamformer filter,                can be 'yes' or 'no'
%   'keepleadfield'    = remember the forward computation,              can be 'yes' or 'no'
%   'keepcsd'          = remember the estimated cross-spectral density, can be 'yes' or 'no'
%   'weightnorm'       = normalize the beamformer weights,              can be 'no', 'unitnoisegain' or 'nai'
%
% These options influence the forward computation of the leadfield
%   'reducerank'      = 'no' or number (default = 3 for EEG, 2 for MEG)
%   'backproject'     = 'yes' or 'no', in the case of a rank reduction this parameter determines whether the result will be backprojected onto the original subspace (default = 'yes')
%   'normalize'       = 'no', 'yes' or 'column' (default = 'no')
%   'normalizeparam'  = parameter for depth normalization (default = 0.5)
%   'weight'          = number or Nx1 vector, weight for each dipole position to compensate for the size of the corresponding patch (default = 1)
%
% These options influence the mathematical inversion of the cross-spectral density matrix
%  'lambda'           = regularisation parameter
%  'kappa'            = parameter for covariance matrix inversion
%  'tol'              = parameter for covariance matrix inversion
%
% If the dipole definition only specifies the dipole location, a rotating
% dipole (regional source) is assumed on each location. If a dipole moment
% is specified, its orientation will be used and only the strength will
% be fitted to the data.
%
% See also FT_SOURCEANALYSIS, FT_PREPARE_HEADMODEL, FT_PREPARE_SOURCEMODEL

% Copyright (C) 2003-2020, Robert Oostenveld and Jan-Mathijs Schoffelen
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    FieldTrip is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

if mod(nargin-5,2)
  % the first 5 arguments are fixed, the other arguments should come in pairs
  ft_error('invalid number of optional arguments');
end

% get the optional input arguments, or use defaults
Pr             = ft_getopt(varargin, 'Pr');
Cr             = ft_getopt(varargin, 'Cr');
refdip         = ft_getopt(varargin, 'refdip');
powmethod      = ft_getopt(varargin, 'powmethod');  % the default for this is set below
realfilter     = ft_getopt(varargin, 'realfilter'); % the default for this is set below
subspace       = ft_getopt(varargin, 'subspace');
feedback       = ft_getopt(varargin, 'feedback',      'text');
keepcsd        = ft_getopt(varargin, 'keepcsd',       'no');
keepfilter     = ft_getopt(varargin, 'keepfilter',    'no');
keepleadfield  = ft_getopt(varargin, 'keepleadfield', 'no');
projectnoise   = ft_getopt(varargin, 'projectnoise',  'yes');
fixedori       = ft_getopt(varargin, 'fixedori',      'no');
weightnorm     = ft_getopt(varargin, 'weightnorm',    'no');

% construct the low-level options for the covariance matrix inversion as key-value pairs, these are passed to FT_INV
invopt = {};
invopt = ft_setopt(invopt, 'lambda',    ft_getopt(varargin, 'lambda', 0));
invopt = ft_setopt(invopt, 'kappa',     ft_getopt(varargin, 'kappa'));
invopt = ft_setopt(invopt, 'tolerance', ft_getopt(varargin, 'tol'));
invopt = ft_setopt(invopt, 'method',    ft_getopt(varargin, 'invmethod'));

% construct the low-level options for the leadfield computation as key-value pairs, these are passed to FT_COMPUTE_LEADFIELD
leadfieldopt = {};
leadfieldopt = ft_setopt(leadfieldopt, 'reducerank',     ft_getopt(varargin, 'reducerank'));
leadfieldopt = ft_setopt(leadfieldopt, 'backproject',    ft_getopt(varargin, 'backproject'));
leadfieldopt = ft_setopt(leadfieldopt, 'normalize',      ft_getopt(varargin, 'normalize'));
leadfieldopt = ft_setopt(leadfieldopt, 'normalizeparam', ft_getopt(varargin, 'normalizeparam'));
leadfieldopt = ft_setopt(leadfieldopt, 'weight',         ft_getopt(varargin, 'weight'));

% convert the yes/no arguments to the corresponding logical values
keepfilter     = istrue(keepfilter);
keepleadfield  = istrue(keepleadfield);
keepcsd        = istrue(keepcsd);
projectnoise   = istrue(projectnoise);
fixedori       = istrue(fixedori);

% FIXME besides regular/complex lambda1, also implement a real version

% default is to use the largest singular value of the csd matrix, see Gross 2001
if isempty(powmethod)
  powmethod = 'lambda1';
end

% default is to be consistent with the original description of DICS in Gross 2001
if isempty(realfilter)
  realfilter = 'no';
end

% use these two logical flags instead of doing the string comparisons each time again
powtrace   = strcmp(powmethod, 'trace');
powlambda1 = strcmp(powmethod, 'lambda1');

if ~isempty(Cr)
  % ensure that the cross-spectral density with the reference signal is a column matrix
  Cr = Cr(:);
end

if isfield(sourcemodel, 'mom') && fixedori
  ft_error('you cannot specify a dipole orientation and fixedori simultaneously');
end

% flags to avoid calling isfield repeatedly in the loop over grid positions (saves a lot of time)
hasmom        = isfield(sourcemodel, 'mom');
hasleadfield  = isfield(sourcemodel, 'leadfield');
hasfilter     = isfield(sourcemodel, 'filter');
hassubspace   = isfield(sourcemodel, 'subspace');

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% find the dipole positions that are inside/outside the brain
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if ~isfield(sourcemodel, 'inside')
  if hasfilter
    sourcemodel.inside = ~cellfun(@isempty, sourcemodel.filter);
  elseif hasleadfield
    sourcemodel.inside = ~cellfun(@isempty, sourcemodel.leadfield);
  else
    sourcemodel.inside = ft_inside_headmodel(sourcemodel.pos, headmodel);
  end
end

% convert to logical representation
sourcemodel = fixinside(sourcemodel);

if hasfilter && (fixedori || ~isequal(weightnorm, 'no'))
  ft_warning('with precomputed spatial filters a fixed orientation constraint or weight normalisation options are not applied');
end

% keep the original details on inside and outside positions
originside = sourcemodel.inside;
origpos    = sourcemodel.pos;

% select only the dipole positions inside the brain for scanning
sourcemodel.pos    = sourcemodel.pos(originside,:);
sourcemodel.inside = true(size(sourcemodel.pos,1),1);

if hasmom
  sourcemodel.mom = sourcemodel.mom(:,originside);
end

if hasfilter
  ft_info('using precomputed filters\n');
  sourcemodel.filter = sourcemodel.filter(originside);
elseif hasleadfield
  ft_info('using precomputed leadfields\n');
  sourcemodel.leadfield = sourcemodel.leadfield(originside);
else
  ft_info('computing forward model on the fly\n');
end

if hassubspace
  ft_info('using subspace projection\n');
  sourcemodel.subspace = sourcemodel.subspace(originside);
end

% dics has the following sub-methods, which depend on the function input arguments
% power only, cortico-muscular coherence and cortico-cortical coherence
if ~isempty(Cr) && ~isempty(Pr) && isempty(refdip)
  % compute cortico-muscular coherence, using reference cross spectral density
  submethod = 'dics_refchan';
elseif isempty(Cr) && isempty(Pr) && ~isempty(refdip)
  % compute cortico-cortical coherence with a dipole at the reference position
  submethod = 'dics_refdip';
  % check for forbidden options in combination with this submethod
  if hassubspace || ~isempty(subspace)
    ft_error('subspace projections are not supported for beaming cortico-cortical coherence');
  end
  if fixedori
    ft_error('fixed orientations are not supported for beaming cortico-cortical coherence');
  end
elseif isempty(Cr) && isempty(Pr) && isempty(refdip)
  % only compute power of a dipole at the grid positions
  submethod = 'dics_power';
else
  ft_error('invalid combination of input arguments for dics');
end

% it is difficult to give a quantitative estimate of lambda, therefore also
% support relative (percentage) measure that can be specified as string (e.g. '10%')
% the converted value needs to be passed on to ft_inv
lambda = ft_getopt(invopt, 'lambda');
if ~isempty(lambda) && ischar(lambda) && lambda(end)=='%'
  ratio  = sscanf(lambda, '%f%%');
  ratio  = ratio/100;
  lambda = ratio * trace(C)/size(C,1);
  if ~hassubspace
    invopt = ft_setopt(invopt, 'lambda', lambda);
  end
end

if projectnoise || strcmp(weightnorm, 'nai')
  % estimate the noise level in the covariance matrix by the smallest (non-zero) singular value
  % always needed for the NAI weight normalization case
  noise = svd(C);
  noise = noise(rank(C));
  % estimated noise floor is equal to or higher than lambda
  noise = max(noise, lambda);
end

% the inverse of the cross-spectral density matrix only has to be computed once for all dipoles
if hassubspace
  ft_notice('using source-specific subspace projection\n');
  % remember the original data prior to the voxel dependent subspace projection
  dat_pre_subspace = dat;
  C_pre_subspace = C;
  if strcmp(submethod, 'dics_refchan')
    Cr_pre_subspace = Cr;
    Pr_pre_subspace = Pr;
  end
elseif ~isempty(subspace)
  ft_notice('using data-specific subspace projection\n');
  % TODO implement an "eigenspace beamformer" as described in Sekihara et al. 2002 in HBM
  if numel(subspace)==1
    % interpret this as a truncation of the eigenvalue-spectrum
    % if <1 it is a fraction of the largest eigenvalue
    % if >=1 it is the number of largest eigenvalues
    dat_pre_subspace = dat;
    C_pre_subspace  = C;
    [u, s, v] = svd(real(C));
    if subspace<1
      subspace  = find(diag(s)./s(1,1) > subspace, 1, 'last');
    end

    C       = s(1:subspace,1:subspace);
    % this is equivalent to subspace*C*subspace' but behaves well numerically
    % by construction.
    invC     = diag(1./diag(C + lambda * eye(size(C))));
    subspace = u(:,1:subspace)';
    if ~isempty(dat), dat = subspace*dat; end

    if strcmp(submethod, 'dics_refchan')
      Cr = subspace*Cr;
    end

  else
    C_pre_subspace  = C;
    C               = subspace*C*subspace';
    % here the subspace can be different from the singular vectors of C, so we
    % have to do the sandwiching as opposed to line 256
    if strcmp(realfilter, 'yes')
      invC = ft_inv(real(C), invopt{:});
    else
      invC = ft_inv(C, invopt{:});
    end

    if strcmp(submethod, 'dics_refchan')
      Cr = subspace*Cr;
    end
  end
else
  if strcmp(realfilter, 'yes')
    % the filter is computed using only the leadfield and the inverse covariance or CSD matrix
    % therefore using the real-valued part of the CSD matrix here ensures a real-valued filter
    invC = ft_inv(real(C), invopt{:});
  else
    invC = ft_inv(C, invopt{:});
  end
end

if ~hassubspace
  % compute the square of invC, which might be needed for unitnoisegain or NAI constraint
  invC_squared = invC^2;
end

if strcmp(submethod, 'dics_refdip')
  % handle the reference dipole, this needs to be done only once, since in this implementation
  % no fancy pairwise dipole source model is assumed
  if isstruct(refdip) && isfield(refdip, 'filter') % check if precomputed filter is present
    assert(iscell(refdip.filter) && numel(refdip.filter)==1);
    filt1 = refdip.filter{1};
  else
    if ~isstruct(refdip)
      % this is very old behavior: refdip is assumed to be a 1x3 position vector
      refpos     = refdip; clear refdip;
      refdip.pos = refpos;
    end
    % now refdip is always a struct
    if isfield(refdip, 'leadfield') % check if precomputed leadfield is present
      assert(iscell(refdip.leadfield) && numel(refdip.leadfield)==1);
      lf1 = refdip.leadfield{1};
    elseif isfield(refdip, 'pos')
      assert(isnumeric(refdip.pos) && numel(refdip.pos)==3);
      lf1 = ft_compute_leadfield(refdip.pos, sens, headmodel, leadfieldopt{:});
    end
    if isfield(refdip, 'mom') % check for fixed orientation
      lf1 = lf1 * refdip.mom(:);
    end

    % construct the spatial filter
    switch weightnorm
      case 'nai'
        % Van Veen's Neural Activity Index
        ft_error('vector version of nai weight normalization is not implemented');
      case 'unitnoisegain'
        % filt*filt' = I
        % Unit-noise gain minimum variance (aka Borgiotti-Kaplan) beamformer
        denom = pinv(lf1' * invC * lf1);
        gamma = denom * (lf1' * invC_squared * lf1) * denom;

        % compute the spatial filter, as per eqn. 4.85
        filt1 = diag(1./sqrt(diag(gamma))) * denom * lf1' * invC;
      case 'arraygain'
        % filt*lf = ||lf||, applies to scalar leadfield, and to one of the possibilities of the vector version, eqn. 4.75
        lfn   = lf1./norm(lf1);
        filt1 = pinv(lfn' * invC * lfn) * lfn' * invC; % S&N eqn. 4.09 (scalar version), and eqn. 4.75 (vector version)
      case {'unitgain' 'no'}
        % this is the 'standard' unit gain constraint spatial filter: filt*lf=I, applies both to vector and scalar leadfields
        filt1 = pinv(lf1' * invC * lf1) * lf1' * invC; % Gross eqn. 3 & van Veen eqn. 23, use PINV/SVD to cover rank deficient leadfield
      otherwise
        ft_error('unsupported option for weightnorm');
    end
  end

  % compute the power of the reference dipole location
  if powlambda1
    Pr = lambda1(filt1 * C * ctranspose(filt1));      % compute the power at the first dipole location, Gross eqn. 8
  elseif powtrace
    Pr = real(trace(filt1 * C * ctranspose(filt1)));  % compute the power at the first dipole location
  end
end % if dics_refdip

% start the scanning with the proper metric
ft_progress('init', feedback, 'scanning grid');
for i=1:size(sourcemodel.pos,1)
  ft_progress(i/size(sourcemodel.pos,1), 'scanning grid %d/%d\n', i, size(sourcemodel.pos,1));

  if hasfilter
    % precomputed filter is provided, the leadfield is not needed
    filt = sourcemodel.filter{i};

  else
    if hasleadfield && hasmom && size(sourcemodel.mom, 1)==size(sourcemodel.leadfield{i}, 2)
      % reuse the leadfield that was previously computed and project
      lf = sourcemodel.leadfield{i} * sourcemodel.mom(:,i);
    elseif  hasleadfield &&  hasmom
      % reuse the leadfield that was previously computed but don't project
      lf = sourcemodel.leadfield{i};
    elseif  hasleadfield && ~hasmom
      % reuse the leadfield that was previously computed
      lf = sourcemodel.leadfield{i};
    elseif ~hasleadfield &&  hasmom
      % compute the leadfield for a fixed dipole orientation
      lf = ft_compute_leadfield(sourcemodel.pos(i,:), sens, headmodel, leadfieldopt{:}) * sourcemodel.mom(:,i);
    else
      % compute the leadfield
      lf = ft_compute_leadfield(sourcemodel.pos(i,:), sens, headmodel, leadfieldopt{:});
    end

    if hassubspace
      % do subspace projection of the forward model
      lforig = lf;
      lf = sourcemodel.subspace{i} * lf;
      % the cross-spectral density becomes voxel dependent due to the projection
      C    = sourcemodel.subspace{i} * C_pre_subspace * sourcemodel.subspace{i}';
      if strcmp(realfilter, 'yes')
        invC = ft_inv(sourcemodel.subspace{i} * real(C_pre_subspace) * sourcemodel.subspace{i}', invopt{:});
      else
        invC = ft_inv(sourcemodel.subspace{i} * C_pre_subspace * sourcemodel.subspace{i}', invopt{:});
      end
    elseif ~isempty(subspace)
      % do subspace projection of the forward model only
      lforig = lf;
      lf     = subspace * lf;

      % according to Kensuke's paper, the eigenspace bf boils down to projecting
      % the 'traditional' filter onto the subspace spanned by the first k eigenvectors
      % [u,s,v] = svd(C); filt = ESES*filt; ESES = u(:,1:k)*u(:,1:k)';
      % however, even though it seems that the shape of the filter is identical to
      % the shape it is obtained with the following code, the w*lf=I does not hold.
    end

    if fixedori
      switch(weightnorm)
        case {'unitnoisegain','nai'}
          % optimal orientation calculation for unit-noise gain beamformer,
          % (also applies nai weightnorm constraint), based on equation 4.47 from Sekihara & Nagarajan (2008)
          % the following is a reformulation of the generalized eigenproblem
          [v, d]        = eig(pinv(lf' * invC_squared *lf)*(lf' * invC *lf));
          [d, iv]       = sort(diag(d), 'descend');
          unitnoiseori  = v(:,iv(1));
          lf            = lf * unitnoiseori;
          estimate.ori{i} = unitnoiseori;
          estimate.eta(i) = d(1)./d(2); % ratio between largest and second largest eigenvalues
          if hassubspace, lforig = lforig * unitnoiseori; end

        case 'arraygain'
          % optimal orientation calculation for array-gain beamformer, Sekihara & Nagarajan eqn. 4.44
          [v, d]        = eig(pinv(lf' * invC *lf)*(lf' * lf));
          [d, iv]       = sort(diag(d), 'descend');
          arraygainori  = v(:,iv(1));
          lf            = lf * arraygainori;
          estimate.ori{i} = arraygainori;
          estimate.eta(i) = d(1)./d(2); % ratio between largest and second largest eigenvalues
          if hassubspace, lforig = lforig * arraygainori; end

        otherwise
          % compute the leadfield for the optimal dipole orientation that maximizes spatial filter output
          % subsequently the leadfield for only that dipole orientation will be used for the final filter computation
          % filt = pinv(lf' * invC * lf) * lf' * invC;
          % [u, s, v] = svd(real(filt * C * ctranspose(filt)));
          % in this step the filter computation is not necessary, use the quick way to compute the voxel level covariance (cf. van Veen 1997)
          [u, s, v]     = svd(real(pinv(lf' * invC *lf)));
          maxpowori     = u(:,1);
          lf            = lf * maxpowori;
          estimate.ori{i} = maxpowori;
          estimate.eta(i) = s(1,1)./s(2,2); % ratio between the first and second singular values
          if hassubspace, lforig = lforig * maxpowori; end
      end
    end

    % construct the spatial filter
    switch weightnorm
      case 'nai'
        % Van Veen's Neural Activity Index
        % below equation is equivalent to following:
        % filt = pinv(lf' * invC * lf) * lf' * invC;
        % filt = filt/sqrt(noise*filt*filt');
        % the scaling term in the denominator is sqrt of projected noise, as per eqn. 2.67 of Sekihara & Nagarajan 2008 (S&N)
        if fixedori
          filt = pinv(sqrt(noise * lf' * invC_squared * lf)) * lf' *invC; % based on S&N eqn. 4.08
        else
          ft_error('vector version of nai weight normalization is not implemented');
        end
      case 'unitnoisegain'
        % filt*filt' = I
        % Unit-noise gain minimum variance (aka Borgiotti-Kaplan) beamformer
        % below equation is equivalent to following:
        % filt = pinv(lf' * invC * lf) * lf' * invC;
        % filt = filt/sqrt(filt*filt');
        if fixedori
          filt = pinv(sqrt(lf' * invC_squared * lf)) * lf' *invC; % S&N eqn. 4.15
        else
          % compute the matrix that is used for scaling of the filter's rows, as per eqn. 4.83
          denom = pinv(lf' * invC * lf);
          gamma = denom * (lf' * invC_squared * lf) * denom;

          % compute the spatial filter, as per eqn. 4.85
          filt = diag(1./sqrt(diag(gamma))) * denom * lf' * invC;
        end
      case 'arraygain'
        % filt*lf = ||lf||, applies to scalar leadfield, and to one of the possibilities of the vector version, eqn. 4.75
        lfn  = lf./norm(lf);
        filt = pinv(lfn' * invC * lfn) * lfn' * invC; % S&N eqn. 4.09 (scalar version), and eqn. 4.75 (vector version)

      case {'unitgain' 'no'}
        % this is the 'standard' unit gain constraint spatial filter: filt*lf=I, applies both to vector and scalar leadfields
        filt = pinv(lf' * invC * lf) * lf' * invC; % Gross eqn. 3 & van Veen eqn. 23, use PINV/SVD to cover rank deficient leadfield

      otherwise
    end
  end

  cfilt = filt * C * ctranspose(filt); % Gross eqn. 4 and 5
  if powlambda1
    if size(cfilt,1) == 1
      % only 1 orientation, no need to do svd
      estimate.pow(i,1) = real(cfilt);
    else
      estimate.pow(i,1) = lambda1(cfilt);   % compute the power at the dipole location, Gross eqn. 8
    end
  elseif powtrace
    estimate.pow(i,1) = real(trace(cfilt)); % compute the power at the dipole location
  end

  if strcmp(submethod, 'dics_refchan')
    % compute the cross-spectrum between the dipole and the reference channel
    cfilt = filt*Cr; % Gross eqn. 6, replaces cfilt computed above
    if powlambda1
      coh = lambda1(cfilt)^2 / (estimate.pow(i,1) * Pr); % Gross eqn. 9
    elseif powtrace
      coh = norm(cfilt)^2 / (estimate.pow(i,1) * Pr);
    end
    estimate.coh(i,1) = coh;
  elseif strcmp(submethod, 'dics_refdip')
    % compute the cross-spectrum between the reference dipole and the dipole
    cfilt = filt1 * C * ctranspose(filt); % Gross eqn. 6, replaces cfilt computed above
    if powlambda1
      coh = lambda1(cfilt)^2 / (estimate.pow(i,1) * Pr); % Gross eqn. 9
    elseif powtrace
      coh = norm(cfilt)^2 / (estimate.pow(i,1) * Pr);
    end
    estimate.coh(i,1) = coh;
  end

  if keepcsd
    % keep the dipole's csd or the dipole-ref cross-spectrum in the output
    estimate.csd{i,1} = cfilt;
  end

  if projectnoise
    if powlambda1
      estimate.noise(i,1) = noise * lambda1(filt * ctranspose(filt));
    elseif powtrace
      estimate.noise(i,1) = noise * real(trace(filt * ctranspose(filt)));
    end
    if keepcsd
      estimate.noisecsd{i,1} = noise * filt * ctranspose(filt);
    end
  end

  if keepfilter
    if hassubspace
      estimate.filter{i,1} = filt*sourcemodel.subspace{i};
    elseif ~isempty(subspace)
      estimate.filter{i,1} = filt*subspace; % FIXME should this be subspace, or pinv(subspace)?
    else
      estimate.filter{i,1} = filt;
    end
  end

  if keepleadfield
    if ~isempty(subspace)
      estimate.leadfield{i,1} = lforig;
    else
      estimate.leadfield{i,1} = lf;
    end
  end

end % for each dipole position
ft_progress('close');

% reassign the estimated values over the inside and outside grid positions
estimate.inside  = originside;
estimate.pos     = origpos;

fnames_cell   = {'leadfield' 'filter' 'ori' 'csd' 'noisecsd' 'subspace'};
for k = 1:numel(fnames_cell)
  if isfield(estimate, fnames_cell{k})
    estimate.(fnames_cell{k})( originside) = estimate.(fnames_cell{k});
    estimate.(fnames_cell{k})(~originside) = {[]};
  end
end

fnames_scalar = {'pow' 'noise' 'eta' 'coh'};
for k = 1:numel(fnames_scalar)
  if isfield(estimate, fnames_scalar{k})
    estimate.(fnames_scalar{k})( originside) = estimate.(fnames_scalar{k});
    estimate.(fnames_scalar{k})(~originside) = nan;
  end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% helper function to obtain the largest singular value
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [s, ori] = lambda1(x)
% determine the largest singular value, which corresponds to the power along the dominant direction
[u, s, v] = svd(x);
s   = s(1);
ori = u(:,1);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% helper function to compute the pseudo inverse. This is the same as the
% standard MATLAB function, except that the default tolerance is twice as
% high.
%   Copyright 1984-2004 The MathWorks, Inc.
%   $Revision$  $Date: 2009/06/17 13:40:37 $
%   default tolerance increased by factor 2 (Robert Oostenveld, 7 Feb 2004)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function X = pinv(A,varargin)
[m,n] = size(A);
if n > m
  X = pinv(A',varargin{:})';
else
  [U,S,V] = svd(A,0);
  if m > 1, s = diag(S);
  elseif m == 1, s = S(1);
  else s = 0;
  end
  if nargin == 2
    tol = varargin{1};
  else
    tol = 10 * max(m,n) * max(s) * eps;
  end
  r = sum(s > tol);
  if (r == 0)
    X = zeros(size(A'),class(A));
  else
    s = diag(ones(r,1)./s(1:r));
    X = V(:,1:r)*s*U(:,1:r)';
  end
end


%tRNA Aminoacylation
%
% @wholeCellModelID Process_tRNAAminoacylation
% @name             tRNA Aminoacylation
% @description
%   Biology
%   ===============
%   In biological systems tRNAs serve as mediators between the ribosome and
%   the amino acids which forms peptide polymers. In this process we
%   simulate the conjugation of amino acids to the tRNAs which deliver them
%   to the ribosome. Additionally we simulate the aminoacylation of the
%   tmRNA which similarly delivers the amino acid alanine to stalled
%   ribosomes.
%
%   Knowledge Base
%   ===============
%   As of 8/11/2010 the M. genitalium knowledge base contains 39 tRNA
%   aminoacylation reactions involving
%   - 37 aminoacylation reactions, each assuming a cost of 1 ATP per
%     aminoacylation
%   - 2 transfer reactions
%   - 36 tRNAs and 1 tmRNA
%   - 21 enzymes
%   - 20 amino acid substrates and 10 additional substrates
%
%   The reaction kinetics stored in the knowledge base were compiled
%   several sources including SABIO-RK [PUB_0100].
%
%   Note: Unlike knowledge base representation, for transfer reactions
%   reactionStoichiometryMatrix doesn't include the already conjugated
%   amino acid on the left-hand-side or the the ultimate conjugated amino
%   acid on the right-hand-side.
%
%   Representation
%   ===============
%   substrates represents the counts of free metabolites available for tRNA
%   aminoacylation. enzymes represents the counts of proteins available to
%   catalyze tRNA aminoacylation reactions. freeRNAs and aminoacylatedRNAs
%   represent the counts of free, unaminoacylated and aminocylated RNAs. We do
%   not represent intermediate states in the aminoacylation of tRNAs.  The
%   molecular weights of the unaminoacylated and aminocylated tRNAs are computed
%   by the knowledge base RNA classes.
%
%   reactionStoichiometryMatrix, reactionModificationMatrix,
%   reactionCatalysisMatrix, and enzymeBounds represent the tRNA aminoacylation
%   reactions. reactionStoichiometryMatrix represents the free metabolites
%   required for  each reaction. reactionModificationMatrix represents the tRNA
%   aminoacylated by each reaction. reactionCatalysis represents the enzyme
%   required to aminoacylate each tRNA. enzymeBounds represents the kcat of the
%   catalyzing enzyme of each reaction. Note: reactionStoichiometryMatrix here
%   is slightly modified from that computed by the super class: amino acids
%   conjugated to tRNAs before and after transfer reactions have been removed
%   from the left- and righ-hand-side of reactionStoichiometryMatrix.
%
%   Initialization
%   ===============
%   tRNAs are all initialized to the aminoacylated state.
%
%   Simulation
%   ===============
%   Uses greedy algorithm to simulate tRNA aminacylation (complexation of
%   tRNAs with the specific amino acids that aminoacylate them).
%   1. Deterministically activate tRNAs up to the minimum of free tRNAs and
%      amino acids, proportional to free tRNAs
%   2. Stoichastically activate residual tRNAs using residual amino acids
%      with probabilities proportional to remaining free tRNAs
%
%      While(true)
%        1. Calculate numbers of protein monomers that can be modified based on
%           substrate, enzyme, and unmodified protein monomer availability and
%           kinetics.
%        2. Randomly select protein monomer to modify weighted by limits
%           calculated in step (1).
%        3. Update substrates, enzymes, unmodified protein monomers
%        4. Repeat until insufficient resources to further modify protein
%           monomers
%      End
%
% Author: Markus Covert, mcovert@stanford.edu
% Author: Jayodita Sanghvi, jayodita@stanfod.edu
% Author: Jonathan Karr, jkarr@stanford.edu
% Affilitation: Covert Lab, Department of Bioengineering, Stanford University
% Last updated: 8/11/2010

classdef tRNAAminoacylation < edu.stanford.covert.cell.sim.ReactionProcess
    %property annotations
    properties (Constant)
        optionNames__              = {}; %names of option properties
		fixedConstantNames__       = {   %names of fixed constant properties
			'monomerTRNACounts';
			};
        fittedConstantNames__      = {}; %names of fitted constant properties
        localStateNames__          = {   %names of simulation state properties redundant with timecourses in this or other processes or the simulation
            'freeRNAs';
            'aminoacylatedRNAs'};
    end

    %IDs, names, and local indices
    properties
        substrateIndexs_aminoAcids                   %indices within substrates of amino acids
        substrateIndexs_glutamate                    %index within substrates of glutamate
        substrateIndexs_glutamine                    %index within substrates of glutamine
        substrateIndexs_methionine                   %index within substrates of methionine
        substrateIndexs_fmethionine                  %index within substrates of formylmethionine
        substrateIndexs_atp                          %index within substrates of ATP
        substrateIndexs_amp                          %index within substrates of AMP
        substrateIndexs_adp                          %index within substrates of ADP
        substrateIndexs_diphosphate                  %index within substrates of diphosphate
        substrateIndexs_phosphate                    %index within substrates of inorganic phosphate
        substrateIndexs_water                        %index within substrates of water
        substrateIndexs_hydrogen                     %index within substrates of hydrogen
        substrateIndexs_fthf10                       %index within substrates of 10-forymltetrahydrofolate
        substrateIndexs_thf                          %index within substrates of tetrahydrofolate

        enzymeIndexs_tRNASynthetases                 %indices within enzymes of tRNA synthetases
        enzymeIndexs_tRNATransferases                %indices within enzymes of tRNA transferases
        enzymeIndexs_tRNAGlutamylSynthetase          %index within enzymes of tRNA glutamyl synthetase
        enzymeIndexs_tRNAMethionylSynthetase         %index within enzymes of tRNA methionyl synthetase
        enzymeIndexs_tRNAGlutamylAmidotransferase    %index within enzymes of tRNA glutamyl amidotransferase
        enzymeIndexs_tRNAMethionylFormyltransferase  %index within enzymes of tRNA methionyl formyltransferase
        enzymeIndexs_tRNAs                           %index within enzymes of tRNAs
        enzymeIndexs_tmRNA                           %index within enzymes of tmRNA

        reactionIndexs_aminoacylation                %indices within reactions of tRNA aminoacylation reactions
        reactionIndexs_transfer                      %indices within reactions of tRNA transfer reactions
        reactionIndexs_glutamylamidotransfer         %index within reactions of tRNA glutamylamidotransfer reaction
        reactionIndexs_methionylformyltransfer       %index within reactions of tRNA methionylformyltransfer reaction

        compartmentWholeCellModelIDs                 %whole cell model ids of compartments
        compartmentIndexs_cytosol                    %index within compartments of cytosol

        freeRNAWholeCellModelIDs                     %whole cell model ids of free RNAs
        aminoacylatedRNAWholeCellModelIDs            %whole cell model ids of aminoacylated RNAs

        aminoacylatedRNAGlobalIndexs                 %indices of aminoacylated RNAs within simulation.matureRNAIndexs
        aminoacylatedRNAtRNAIndexs                   %indices of aminoacylated tRNAs within aminoacylatedRNAGlobalIndexs

        speciesIndexs_enzymes                        %indexs of enzyme species within speciesReactantByproductMatrix, speciesReactantMatrix
    end

    %fixed biological constants
    properties
        monomerTRNACounts              %numbers of each tRNA species required to translate each protein monomer species

        speciesReactantByproductMatrix %stoichiometry of susbtrates, enzymes, RNA in modifications necessary to mature each modifying RNA: [RNA modifications] X [subtrates; enzymes; free RNAs]
        speciesReactantMatrix          %reactant stoichiometry of susbtrates, enzymes, RNA in modifications necessary to mature each modifying RNA: [RNA modifications] X [subtrates; enzymes; free RNAs]
    end

    %global state (stored locally for convenience)
    properties
        freeRNAs          %counts of free RNAs
        aminoacylatedRNAs %counts of aminoacylated RNAs
    end

    %dependent global state (implemented as dependent property for
    %convenience)
    properties (Dependent)
        tRNASynthetases   %counts of tRNA synthetases
        tRNATransferases  %counts of tRNA transferases
    end

    %constructor
    methods
        function this = tRNAAminoacylation(wholeCellModelID, name)
            this = this@edu.stanford.covert.cell.sim.ReactionProcess(wholeCellModelID, name);
        end
    end

    %communication between process/simulation
    methods
        %initialize constants
        function initializeConstants(this, knowledgeBase, simulation, varargin)
            %import classes
            import edu.stanford.covert.util.ComputationUtil;

            %call super class method to get knowledge base reactions
            this.initializeConstants@edu.stanford.covert.cell.sim.ReactionProcess(knowledgeBase, simulation, varargin{:});

            %compartments
            this.compartmentWholeCellModelIDs = this.compartment.wholeCellModelIDs(this.compartment.cytosolIndexs);
            this.compartmentIndexs_cytosol    = 1;

            %create representation for GLN which super class leaves off
            %because it cancels out in the knowledge base
            this.substrateWholeCellModelIDs  = [this.substrateWholeCellModelIDs; 'GLN'];
            this.reactionStoichiometryMatrix = [this.reactionStoichiometryMatrix; zeros(1, size(this.reactionStoichiometryMatrix, 2))];
            this.reactionCoenzymeMatrix      = [this.reactionCoenzymeMatrix zeros(size(this.reactionCoenzymeMatrix, 1), 1)];

            %substrate indices
            this.substrateIndexs_aminoAcids  = this.substrateIndexs({'ALA'; 'ARG'; 'ASN'; 'ASP'; 'CYS'; 'GLN'; 'GLU'; 'GLY'; 'HIS'; 'ILE'; 'LEU'; 'LYS'; 'MET'; 'PHE'; 'PRO'; 'SER'; 'THR'; 'TRP'; 'TYR'; 'VAL'});
            this.substrateIndexs_glutamate   = this.substrateIndexs({'GLU'});
            this.substrateIndexs_glutamine   = this.substrateIndexs({'GLN'});
            this.substrateIndexs_methionine  = this.substrateIndexs({'MET'});
            this.substrateIndexs_fmethionine = this.substrateIndexs({'FMET'});
            this.substrateIndexs_atp         = this.substrateIndexs({'ATP'});
            this.substrateIndexs_amp         = this.substrateIndexs({'AMP'});
            this.substrateIndexs_adp         = this.substrateIndexs({'ADP'});
            this.substrateIndexs_diphosphate = this.substrateIndexs({'PPI'});
            this.substrateIndexs_phosphate   = this.substrateIndexs({'PI'});
            this.substrateIndexs_water       = this.substrateIndexs({'H2O'});
            this.substrateIndexs_hydrogen    = this.substrateIndexs({'H'});
            this.substrateIndexs_fthf10      = this.substrateIndexs({'FTHF10'});
            this.substrateIndexs_thf         = this.substrateIndexs({'THF'});

            %include tRNAs as enzymes
            this.enzymeWholeCellModelIDs = [
                this.enzymeWholeCellModelIDs;
                this.rna.wholeCellModelIDs(this.rna.matureIndexs(this.rna.matureTRNAIndexs));
                'MG_0004'];

            %enzyme indices
            this.enzymeIndexs_tRNASynthetases = this.enzymeIndexs({
                'MG_292_TETRAMER';       %alanyl-tRNA synthetase
                'MG_378_MONOMER';        %arginyl-tRNA synthetase
                'MG_036_DIMER';          %aspartyl-tRNA synthetase
                'MG_113_DIMER';          %asparaginyl-tRNA synthetase
                'MG_253_MONOMER';        %cysteinyl-tRNA synthetase
                'MG_462_MONOMER';        %glutamyl-tRNA synthetase
                'MG_251_DIMER';          %glycyl-tRNA synthetase
                'MG_035_DIMER';          %histidyl-tRNA synthetase
                'MG_345_MONOMER';        %isoleucyl-tRNA synthetase
                'MG_266_MONOMER';        %leucyl-tRNA synthetase
                'MG_136_DIMER';          %lysyl-tRNA synthetase
                'MG_021_DIMER';          %methionyl-tRNA synthetase
                'MG_194_195_TETRAMER';   %phenylalanyl-tRNA synthetase
                'MG_283_DIMER';          %prolyl-tRNA synthetase
                'MG_005_DIMER';          %seryl-tRNA synthetase
                'MG_375_DIMER';          %threonyl-tRNA synthetase
                'MG_126_DIMER';          %tryptophanyl-tRNA synthetase
                'MG_455_DIMER';          %tyrosyl-tRNA synthetase
                'MG_334_MONOMER'});      %valyl-tRNA synthetase
            this.enzymeIndexs_tRNATransferases = this.enzymeIndexs({
                'MG_098_099_100_TRIMER'; %glutamyl-tRNA(Gln) and/or aspartyl-tRNA(Asn) amidotransferase
                'MG_365_MONOMER'});      %methionyl-tRNA formyltransferase
            this.enzymeIndexs_tRNAGlutamylSynthetase         = this.enzymeIndexs({'MG_462_MONOMER'});        %glutamyl-tRNA synthetase
            this.enzymeIndexs_tRNAMethionylSynthetase        = this.enzymeIndexs({'MG_021_DIMER'});          %methionyl-tRNA synthetase
            this.enzymeIndexs_tRNAGlutamylAmidotransferase   = this.enzymeIndexs({'MG_098_099_100_TRIMER'}); %glutamyl-tRNA(Gln) and/or aspartyl-tRNA(Asn) amidotransferase
            this.enzymeIndexs_tRNAMethionylFormyltransferase = this.enzymeIndexs({'MG_365_MONOMER'});        %methionyl-tRNA formyltransferase
            this.enzymeIndexs_tRNAs                          = this.enzymeIndexs(this.rna.wholeCellModelIDs(this.rna.matureIndexs(this.rna.matureTRNAIndexs)));
            this.enzymeIndexs_tmRNA                          = this.enzymeIndexs({'MG_0004'});

            %RNAs
            this.reactionModificationMatrix(isnan(this.reactionModificationMatrix)) = 1;

            aaGenes = any(this.reactionModificationMatrix, 1)';
            aaRNAs  = ComputationUtil.invertCompositionMatrix(this.rna.matureRNAGeneComposition) * aaGenes;
            this.aminoacylatedRNAGlobalIndexs  = find(aaRNAs);
            this.aminoacylatedRNAtRNAIndexs    = intersect(this.aminoacylatedRNAGlobalIndexs, this.rna.matureTRNAIndexs);

            this.freeRNAWholeCellModelIDs          = this.rna.wholeCellModelIDs(this.rna.matureIndexs(this.aminoacylatedRNAGlobalIndexs));
            this.aminoacylatedRNAWholeCellModelIDs = this.rna.wholeCellModelIDs(this.rna.aminoacylatedIndexs(this.aminoacylatedRNAGlobalIndexs));

            this.reactionModificationMatrix = this.reactionModificationMatrix(:, aaGenes);

            %reaction indices
            this.reactionIndexs_aminoacylation          = find(strcmp(this.reactionTypes, 'aminoacylation'));
            this.reactionIndexs_transfer                = find(strcmp(this.reactionTypes, 'transfer'));
            this.reactionIndexs_glutamylamidotransfer   = this.reactionIndexs({'MG502_Amidotransferase'});
            this.reactionIndexs_methionylformyltransfer = this.reactionIndexs({'MG488_Formyltransferase'});

            %for transfer reactions unlike knowledge base representation,
            %formulate reactionStoichiometryMatrix so that left-hand-side
            %doesn't include the already conjugated amino acid, and the
            %right-hand-side doesn't include the ultimate conjugated amino
            %acid
            this.reactionStoichiometryMatrix(this.substrateIndexs_glutamine,   this.reactionIndexs_glutamylamidotransfer)   = -1;
            this.reactionStoichiometryMatrix(this.substrateIndexs_glutamate,   this.reactionIndexs_glutamylamidotransfer)   =  1;
            this.reactionStoichiometryMatrix(this.substrateIndexs_methionine,  this.reactionIndexs_methionylformyltransfer) =  0;
            this.reactionStoichiometryMatrix(this.substrateIndexs_fmethionine, this.reactionIndexs_methionylformyltransfer) =  0;

            %re-run super class method to update substrate and enzyme
            %mappings to include GLN, the tRNAs, and the tmRNA
            this.initializeConstants@edu.stanford.covert.cell.sim.Process(knowledgeBase, simulation, varargin{:});

            this.initializeSpeciesNetwork();

            %protein monomers -- needed for fitting
            this.monomerTRNACounts = knowledgeBase.proteinMonomerTRNACounts;
        end

        %initialize reaction stoichiomety to be used in evolveState:
        %  [tRNA aminoacylations and transfers] X [subtrates; enzymes; free tRNAs]
        function initializeSpeciesNetwork(this)
            % formulate network: [tRNA aminoacylations and transfers] X [subtrates; enzymes; free tRNAs]
            numMetabolites = size(this.reactionStoichiometryMatrix, 1);
            numEnzymes     = size(this.reactionCatalysisMatrix, 2);
            numRNAs        = size(this.reactionModificationMatrix, 2);

            this.speciesReactantByproductMatrix = [this.reactionModificationMatrix' * [ ...
                -this.reactionStoichiometryMatrix' ...
                this.reactionCatalysisMatrix ./ repmat(this.enzymeBounds(:, 2) * this.stepSizeSec, 1, numEnzymes)] ...
                eye(numRNAs)];
            this.speciesReactantMatrix = [this.reactionModificationMatrix' * max(0, [ ...
                -this.reactionStoichiometryMatrix' ...
                this.reactionCatalysisMatrix ./ repmat(this.enzymeBounds(:, 2) * this.stepSizeSec, 1, numEnzymes)]) ...
                eye(numRNAs)];

            this.speciesIndexs_enzymes = (1:numEnzymes)' + numMetabolites;
        end

        %retrieve state from simulation
        function copyFromState(this)
            this.copyFromState@edu.stanford.covert.cell.sim.ReactionProcess();

            this.freeRNAs          = this.rna.counts(this.rna.matureIndexs(       this.aminoacylatedRNAGlobalIndexs), this.compartment.cytosolIndexs, :);
            this.aminoacylatedRNAs = this.rna.counts(this.rna.aminoacylatedIndexs(this.aminoacylatedRNAGlobalIndexs), this.compartment.cytosolIndexs, :);
        end

        %send state to simulation
        function copyToState(this)
            this.copyToState@edu.stanford.covert.cell.sim.ReactionProcess();

            this.rna.counts(this.rna.matureIndexs(       this.aminoacylatedRNAGlobalIndexs), this.compartment.cytosolIndexs, :) = this.freeRNAs;
            this.rna.counts(this.rna.aminoacylatedIndexs(this.aminoacylatedRNAGlobalIndexs), this.compartment.cytosolIndexs, :) = this.aminoacylatedRNAs;
        end
    end

    %allocate memory for state
    methods
        function allocateMemoryForState(this, numTimePoints)
            this.allocateMemoryForState@edu.stanford.covert.cell.sim.ReactionProcess(numTimePoints);

            this.freeRNAs          = zeros(length(this.aminoacylatedRNAGlobalIndexs), 1, numTimePoints);
            this.aminoacylatedRNAs = zeros(length(this.aminoacylatedRNAGlobalIndexs), 1, numTimePoints);
        end
    end

    %model
    methods
        %Calculate
        %- contribution to FBA objective
        %- minimum expression consistent with cell cycle length
        function [bmProd, byProd, minEnzExp, maxEnzExp] = calcResourceRequirements_LifeCycle(this, ~, states)
            %% substrate and byproducts
            %aminoacylations of each t(m)RNA
            [~, idxs] = ismember(this.aminoacylatedRNAtRNAIndexs, this.aminoacylatedRNAGlobalIndexs);
            aminoacylations = zeros(size(this.aminoacylatedRNAGlobalIndexs));
            aminoacylations(idxs) = aminoacylations(idxs) + ...
                this.monomerTRNACounts' * states.monomerProductions;

            %t(m)RNA aminoacylation reactions
            reactions = this.reactionModificationMatrix * aminoacylations;

            %metabolic load of t(m)RNA aminoacylation reactions
            bmProd = max(0, -this.reactionStoichiometryMatrix) * reactions;
            byProd = max(0,  this.reactionStoichiometryMatrix) * reactions;

            %% enzymes
            %initialize
            minEnzExp = zeros(size(this.enzymeWholeCellModelIDs));
            maxEnzExp = Inf(size(this.enzymeWholeCellModelIDs));

            %tRNAs
            minEnzExp(this.enzymeIndexs_tRNAs) = 1.95 * this.monomerTRNACounts' * states.monomerProductions0;

            %tmRNA
            minEnzExp(this.enzymeIndexs_tmRNA) = 0;

            %tRNA synthetases, transferases
            [~, idxs] = ismember(this.aminoacylatedRNAtRNAIndexs, this.aminoacylatedRNAGlobalIndexs);
            aminoacylations = states.rnaProductions0(this.aminoacylatedRNAGlobalIndexs);
            aminoacylations(idxs)                = max(aminoacylations(idxs),                minEnzExp(this.enzymeIndexs_tRNAs));
            aminoacylations(setdiff(1:end,idxs)) = max(aminoacylations(setdiff(1:end,idxs)), minEnzExp(this.enzymeIndexs_tmRNA));

            minEnzExp(setdiff(1:end, [this.enzymeIndexs_tRNAs; this.enzymeIndexs_tmRNA])) = ...
                + minEnzExp(setdiff(1:end, [this.enzymeIndexs_tRNAs; this.enzymeIndexs_tmRNA])) ...
                + 3 * this.reactionCatalysisMatrix' * ((this.reactionModificationMatrix * aminoacylations) ./ this.enzymeBounds(:, 2));
        end

        %initialization: RNAs initialized to mature state by simulation
        %initializeState method. This class initializes RNAs to
        %aminoacylated state.
        function initializeState(this)
            totalRNAs = this.aminoacylatedRNAs + this.freeRNAs;
            this.aminoacylatedRNAs = ceil(2 / 3 * totalRNAs);
            this.freeRNAs = totalRNAs - this.aminoacylatedRNAs;
        end

        %resource requirements
        function result = calcResourceRequirements_Current(this)
            result = max(0, -this.reactionStoichiometryMatrix) * min(...
                this.reactionModificationMatrix * (this.freeRNAs + this.aminoacylatedRNAs + 1), ...
                this.reactionCatalysisMatrix * this.enzymes(1:numel(this.speciesIndexs_enzymes)) * this.stepSizeSec);
        end

        %simulation
        function evolveState(this)
            %terminate early if no free RNAs
            if ~any(this.freeRNAs)
                return;
            end

            numRNAs = numel(this.freeRNAs);

            species = [
                this.substrates;
                this.enzymes(1:numel(this.speciesIndexs_enzymes));
                this.freeRNAs]';

            reactionFluxes = zeros(size(this.speciesReactantByproductMatrix, 1), 1);
            anyFlux = false;

            limits = species(ones(numRNAs, 1), :) ./ this.speciesReactantMatrix;
            limits(:, this.substrateIndexs_water) = NaN;
            limits(:, this.substrateIndexs_hydrogen) = NaN;
            reactionLimits = min(limits, [], 2)';
            reactionLimits(isinf(reactionLimits) | isnan(reactionLimits) | reactionLimits < 0) = 0;
            isReactionInactive = reactionLimits <= 0;

            while true
                %compute maximum number of each species that can be modified
                limits = species(ones(numRNAs, 1), :) ./ max(0, this.speciesReactantByproductMatrix);
                limits(:, this.substrateIndexs_water) = NaN;
                limits(:, this.substrateIndexs_hydrogen) = NaN;
                reactionLimits = min(...
                    this.randStream.stochasticRound(min(limits(:, this.speciesIndexs_enzymes), [], 2)), ...
                    min(limits(:, [1:this.speciesIndexs_enzymes(1)-1  this.speciesIndexs_enzymes(end)+1:end]), [], 2))';
                reactionLimits(isReactionInactive | isinf(reactionLimits) | isnan(reactionLimits) | reactionLimits < 1) = 0;

                %stop if no more tRNA can be aminoacylated
                if ~any(reactionLimits)
                    break;
                end
                anyFlux = true;

                edges = min([0 cumsum(reactionLimits(:)' / sum(reactionLimits))], 1);
                nRxns = min(reactionLimits(reactionLimits > 0));
                if nRxns <= 1
                    %pick substrate, reaction, enzymes
                    [~, selectedReaction] = histc(rand(this.randStream, 1, 1), edges);
                    reactionFluxes(selectedReaction) = ...
                        + reactionFluxes(selectedReaction) ...
                        + 1;

                    %decrement metabolites, enzymes, unmodified substrates; increment modified substrates
                    species = species - this.speciesReactantByproductMatrix(selectedReaction, :);
                else
                    %pick substrate, reaction, enzymes
                    edges(end) = 1.1;
                    selectedReactions = histc(rand(this.randStream, nRxns, 1), edges);
                    selectedReactions = selectedReactions(1:end-1);
                    selectedReactions = selectedReactions(:);
                    reactionFluxes = ...
                        + reactionFluxes ...
                        + selectedReactions;

                    %decrement metabolites, enzymes, unmodified substrates; increment modified substrates
                    species = species - selectedReactions' * this.speciesReactantByproductMatrix;
                end
            end

            %stop if no reaction can proceed
            if ~anyFlux
                return;
            end

            % update substrates
            this.substrates = this.substrates + ...
                this.reactionStoichiometryMatrix * this.reactionModificationMatrix * reactionFluxes;

            % update RNAs
            this.freeRNAs          = this.freeRNAs          - reactionFluxes;
            this.aminoacylatedRNAs = this.aminoacylatedRNAs + reactionFluxes;
        end
    end

    %get methods of dependent global state
    methods
        function value = get.tRNASynthetases(this)
            value = this.enzymes(this.enzymeIndexs_tRNASynthetases,:,:);
        end

        function value = get.tRNATransferases(this)
            value = this.enzymes(this.enzymeIndexs_tRNATransferases,:,:);
        end
    end

    %get methods of dependent local state
    methods
        function value = getDryWeight(this)
            if size(this.freeRNAs, 3) == 1
                value = this.getDryWeight@edu.stanford.covert.cell.sim.ReactionProcess() + (...
                    this.rna.molecularWeights(this.rna.matureIndexs(this.aminoacylatedRNAGlobalIndexs))'        * this.freeRNAs         + ...
                    this.rna.molecularWeights(this.rna.aminoacylatedIndexs(this.aminoacylatedRNAGlobalIndexs))' * this.aminoacylatedRNAs) ...
                    / edu.stanford.covert.util.ConstantUtil.nAvogadro;
            else
                value = this.getDryWeight@edu.stanford.covert.cell.sim.ReactionProcess() + (...
                    permute(this.rna.molecularWeights(this.rna.matureIndexs(this.aminoacylatedRNAGlobalIndexs))'        * permute(this.freeRNAs,         [1 3 2]),[1 3 2]) + ...
                    permute(this.rna.molecularWeights(this.rna.aminoacylatedIndexs(this.aminoacylatedRNAGlobalIndexs))' * permute(this.aminoacylatedRNAs,[1 3 2]),[1 3 2])) ...
                    / edu.stanford.covert.util.ConstantUtil.nAvogadro;
            end
        end
    end
end


function [modelJoint] = createMultipleSpeciesModel(models, varargin)
% Based on the implementation from *Klitgord and Segre 2010, PMID 21124952*.
% The present implementation has been used in *PMID 23022739*, *PMID 25841013*,
% *PMID 25901891*, *PMID 27893703*.
%
% Joins one or more COBRA models with or without another COBRA model
% representing the host. The created setup when a host is entered is
% depicted schematically in Figures 1 and 2 in *PMID 27893703*.
%
% Creates a common space u (lumen) through which all cells can feed and exchange metabolites,
% and separate extracellular spaces for all joined models.
% If a host model is entered, a separate compartment `b` (body fluids) which has no
% connection to extracellular space is created for the host. Metabolites can be transported from
% the lumen to `e`, from `e` to the cytosol and from the cytosol to `b`,
% but not from body fluids to the lumen.
%
% USAGE:
%
%    [modelJoint] = createMultipleSpeciesModel(models, varargin)
%
% INPUTS:
%    models:            cell array of COBRA models(at least one).
%                       Format
%
%                         * models{1,1} = model 1
%                         * models{2,1} = model 2...
%
% OPTIONAL INPUTS:
%    nameTagsModels:    cell array of tags for reaction/metabolite abbreviation
%                       corresponding to each model.
%                       Format
%                         * nameTagsModels{1,1} = 'name tag 1'
%                         * nameTagsModels{2,1} = 'name tag 2'...
%    modelHost:         COBRA model for host
%    nameTagHost:       string of tag for reaction/metabolite abbreviation of host model
%    mergeGenesFlag:    If true, the gene associations in both models are
%                       included in the joined model. If false, empty fields are created
%                       instead (default:false). Note: merging genes is time-consuming
%                       and may crash certain models.
%    remCyclesFlag:     If true, the function will attempt to remove futile
%                       cycles that appear after merging the models (default: false).
%
% OUTPUT:
%    modelJoint:        model structure for joint model
%
% .. Authors:
%       - Ines Thiele and Almut Heinken, 2011-2018
%       - Almut Heinken, 07.02.2018-included option whether or not genes are
%         merged
%       - Almut Heinken, 21.02.2018-fixed compatibility issue with reconstructions
%         from BIGG Models database that have _e instead of [e] as compartment IDs
%       - Almut Heinken, 06.03.2018-changed to parameter-input pairs
%       - Laurent Heirendt, 16/3/2018 - backward compatibility
%       - Almut Heinken, 15.01.2019-fixed compatibility issue with reconstructions
%         from KBase database that have [e0] instead of [e] as compartment IDs
%       - Almut Heinken, 01/2020-remove futile cycles in multi-species
%       AGORA models
%       - Almut Heinken, 07/2022-made futile cycles removal optional
%
% NOTE:
%    This function assumes, that exchange reactions are identified by
%    containing 'EX' in the reaction name and that no other reactions do have this property!

oldOptionalOrder = {'nameTagsModels', 'modelHost', 'nameTagHost', 'mergeGenesFlag'};

% ensure backward compatibility
if numel(varargin) > 0 && ~ischar(varargin{1})
    tempargin = cell(0);
    for i = 1:numel(varargin)
        if ~isempty(varargin{i})
            tempargin{end + 1} = oldOptionalOrder{i};
            tempargin{end + 1} = varargin{i};
        end
    end
    varargin = tempargin;
end

% Define default input parameters if not specified
parser = inputParser();
parser.addRequired('models', @iscell);
parser.addParameter('nameTagsModels', {}, @iscell);
parser.addParameter('modelHost', {}, @isstruct);
parser.addParameter('nameTagHost', '', @(x) ischar(x) || iscell(x))
parser.addParameter('mergeGenesFlag', false, @(x) isnumeric(x) || islogical(x))
parser.addParameter('remCyclesFlag', false, @(x) isnumeric(x) || islogical(x))

parser.parse(models, varargin{:});

models = parser.Results.models;
nameTagsModels = parser.Results.nameTagsModels;
modelHost = parser.Results.modelHost;
nameTagHost = parser.Results.nameTagHost;
mergeGenesFlag = parser.Results.mergeGenesFlag;
remCyclesFlag = parser.Results.remCyclesFlag;

if isempty(models)
   error('Please enter at least one model!')
end
% prepare the model structures and assign name tags for each model structure if not provided
modelNumber = size(models, 1);

if isempty(nameTagsModels)
    % assign default name tags for microbes
    for i = 1:modelNumber
        nameTagsModels{i, 1} = strcat('model', num2str(i), '_');
    end
else
    if size(nameTagsModels, 1) ~= modelNumber
        error('Number of name tags and joint models needs to be identical!')
    end
end

if isempty(nameTagHost)
    % assign default name tag for host
    nameTagHost = 'Host_';
end

%% ensure compatibility with reconstructions from BIGG Models database
for i = 1:modelNumber
    model = models{i, 1};
    metIndices =~cellfun(@isempty, regexp(model.mets, '_e$'));
    model.mets(metIndices) = strrep(model.mets(metIndices), '_e', '[e]');
    models{i, 1} = model;
end
if ~isempty(modelHost)
metIndices =~cellfun(@isempty, regexp(modelHost.mets, '_e$'));
modelHost.mets(metIndices) = strrep(modelHost.mets(metIndices), '_e', '[e]');
end

%% Ensure compatibility with reconstructions from KBase database
for i = 1:modelNumber
    model = models{i, 1};
    metIndices =~cellfun(@isempty, regexp(model.mets, '\[e0\]$'));
    model.mets(metIndices) = strrep(model.mets(metIndices), '[e0]', '[e]');
    % need workaround for biomass metabolite, otherwise the resulting joint
    % model will be unable to carry biomass flux
    if ~isempty(find(ismember(model.mets, 'cpd11416[c0]')))
        model = addDemandReaction(model, 'cpd11416[c0]');
    end
    models{i, 1} = model;
end
if ~isempty(modelHost)
metIndices =~cellfun(@isempty, regexp(modelHost.mets, '\[e0\]$'));
modelHost.mets(metIndices) = strrep(modelHost.mets(metIndices), '[e0]', '[e]');
end
%% Remove futile cycles that may appear after joining (optional)

if remCyclesFlag
    database = loadVMHDatabase;

    unionRxns={};
    for i = 1:modelNumber
        model=models{i, 1};
        unionRxns=vertcat(unionRxns,model.rxns);
    end
    unionRxns=unique(unionRxns);
    for i = 1:modelNumber
        model=models{i, 1};
        biomassReaction=model.rxns(find(strncmp(model.rxns, 'bio', 3)));
        model = removeFutileCycles(model, biomassReaction, database,unionRxns);
        models{i, 1} = model;
    end
end
%% define some variables
eTag = 'u';
exTag = 'e';
% find exchange reactions for models, but leaves demand and sink reactions
% do this for all models to be added, remove exchange reactions from host while leaving demand and sink reactions
% First, find the minimal number of fields common to all models.
presentinallModels = fieldnames(models{1});
missingFields = {};
for i = 2:modelNumber
    cfields = fieldnames(models{i});
    missingFields = union(missingFields, setxor(cfields, presentinallModels));
    presentinallModels = intersect(presentinallModels, cfields);
end
fprintf('The following fields are missing in several models, they will not be merged:\n');
disp(missingFields);
models = restrictModelsToFields(models, presentinallModels);

modelStorage = cell(modelNumber, 1);
for i = 1:modelNumber
    % a new model each turn
    model = models{i, 1};
    % find exchange reactions and external metabolites
    exmod = model.rxns(strmatch('EX', model.rxns));
    % remove all previously defined exchange reactions
    model = removeRxns(model, exmod);
    % make sure the exchange reactions and changed model are saved under correct name
    modelStorage{i, 1} = model;
end

if ~isempty(modelHost)
    %% with a host
    exmod = modelHost.rxns(strmatch('EX', modelHost.rxns));

    % modelHost = removeRxns(modelHost,ExRH);
    % ExRH = modelHost.rxns(selExcH);
    % ExRH(strmatch('sink',modelHost.rxns(selExcH)))=[];
    % ExRH(strmatch('DM',modelHost.rxns(selExcH)))=[];

    % create a new extracellular space for host
    % find all metabolites in e
    relMetIndex = cellfun(@(x) ~isempty(strfind(x, '[e]')), modelHost.mets);
    relMets = modelHost.mets(relMetIndex);
    relRxns = findRxnsFromMets(modelHost, relMets);  % These are all reactions which are relevant
    rxnIndices = ismember(modelHost.rxns, relRxns);
    Stoich = modelHost.S(:, rxnIndices);
    changedMets = regexprep(modelHost.mets, '\[e\]', '\[b\]');
    modelHost = addMultipleMetabolites(modelHost, setdiff(changedMets, modelHost.mets));
    modelHost = addMultipleReactions(modelHost, strcat(modelHost.rxns(rxnIndices), 'b'), changedMets, Stoich, 'lb', modelHost.lb(rxnIndices), ...
                                    'ub', modelHost.ub(rxnIndices), 'c', modelHost.c(rxnIndices), 'subSystems', repmat({'Host Exchange'}, numel(relRxns), 1));

    % remove exchange reactions from host while leaving demand and sink reactions
    modelHost = removeRxns(modelHost, exmod);

    %% create intercellular space
    % will need to find all extracellular metabolites and duplicate reactions
    % if a host model was input, create the shared compartment for the microbes
    model = modelStorage{1, 1};
    nameTag = nameTagsModels{1, 1};
    [modelJoint, MexGJoint] = createInterSpace(model, nameTag, eTag, exTag);

    % if more than one microbe was input
    if modelNumber > 1
        for i = 1:modelNumber
            model = modelStorage{i, 1};
            nameTag = nameTagsModels{i, 1};
            [model, MexG] = createInterSpace(model, nameTag, eTag, exTag);
            % make sure the changed model is saved under correct name
            modelStorage{i, 1} = model;
            MexGJoint = union(MexG, MexGJoint);
        end
     end

    [modelHost,MexGHost] = createInterSpace(modelHost, nameTagHost, eTag, exTag);
    MexGJoint = union(MexGJoint, MexGHost);

    %% merge the models
    % if more than one microbe was input
    if modelNumber > 1
        for i = 2:modelNumber
            model = modelStorage{i, 1};
            [modelJoint] = mergeTwoModels(modelJoint, model, 1, mergeGenesFlag);
        end
    end
    [modelJoint] = mergeTwoModels(modelJoint,modelHost, 1, mergeGenesFlag);

    modelJoint = addExchangeRxn(modelJoint, unique(MexGJoint));

else
    %% without a host
     % create the shared compartment for the microbes
    model = modelStorage{1, 1};
    nameTag = nameTagsModels{1, 1};
    [modelJoint, MexGJoint] = createInterSpace(model, nameTag, eTag, exTag);
     if modelNumber > 1
        for i = 2:modelNumber
            model = modelStorage{i, 1};
            nameTag = nameTagsModels{i, 1};
            [model, MexG] = createInterSpace(model, nameTag, eTag, exTag);
            modelStorage{i, 1}=model;
            MexGJoint = union(MexG, MexGJoint);
        end
     end

     if modelNumber > 1
        for i = 2:modelNumber
            model = modelStorage{i, 1};
            [modelJoint] = mergeTwoModels(modelJoint, model, 1, mergeGenesFlag);
        end
     end

    modelJoint = addExchangeRxn(modelJoint, unique(MexGJoint));
end
end

%%
function [modelNew,MexG] = createInterSpace(model, nameTag, eTag, exTag)
% create intercellular space
% will need to find all extracellular metabolites and duplicate reactions using them
modelNew = model;
% add name tag to all metabolites and reactions in model
modelNew.mets = strcat(nameTag, model.mets);
modelNew.rxns = strcat(nameTag, model.rxns);
% Get the relevant metabolites
relMetsIndex = cellfun(@(x) ~isempty(strfind(x,'biomass[c]')) || ~isempty(strfind(x,['[', exTag, ']'])),modelNew.mets);
relMets = modelNew.mets(relMetsIndex);
% Define the names of the interspace metabolites
MexG = regexprep(strrep(relMets,nameTag,''),strcat('\[', exTag, '\]'), strcat('\[', eTag, '\]'));
varinput = {};
% Add metNames and metFormulas, if present in the original model
if isfield(modelNew,'metNames')
    varinput{end+1} = 'metNames';
    varinput{end+1} = modelNew.metNames(relMetsIndex);
end
if isfield(modelNew,'metFormulas')
    varinput{end+1} = 'metFormulas';
    varinput{end+1} = modelNew.metFormulas(relMetsIndex);
end
% Add all new Metabolites
modelNew = addMultipleMetabolites(modelNew, MexG, varinput{:});

nExchange = numel(relMets);
% Set the exchanger Stoichiometries (met[e] -> met[u])
stoich = [-speye(nExchange);speye(nExchange)];
% Set the names of the exchangers
rxnNames =  strcat(nameTag, 'IEX_', MexG, 'tr');
% Set the bounds
lbs = repmat(-1000,nExchange,1);
ubs = repmat(1000,nExchange,1);
% Set the subSystem
subSystems = repmat({'Transport, intercellular'},nExchange,1);
% Add all Reactions in one go.
modelNew = addMultipleReactions(modelNew,rxnNames,[relMets;MexG],stoich,'lb',lbs,'ub',ubs,'subSystems',subSystems);

end


function model = setCommonZeroStandardGibbsEnergyOfFormation(model, adjustedMetList)
% Sets all Alberty's cofactor metabolites to have a common thermodynamic baseline.
%
% Sets all the exceptional metabolites to have a common baseline.
% i.e. Standard transformed Gibbs energies of reactants with the baseline adjusted
% for certain paired cofactors e.g. `fad` & `fadh2`, such that the
% difference between the two is the same as in Albertys data but
% the absolute values are consistent with the group contribution data
%
% USAGE:
%
%    model = setCommonZeroStandardGibbsEnergyOfFormation(model, adjustedMetList)
%
% INPUT:
%    model:              Thermodynamic model:
%
%                          * .met(m).dGft0 - standard transformed Gibbs energy of formation(kJ/mol)
%                          * .met(m).dGft0Keq - standard transformed Gibbs energy of formation(kJ/mol)
%                          * .met(m).dGft0Source - origin of data, `Keq` or `groupContFileName.txt`
%                          * .met(m).dGft0GroupCont - group. cont. estimate of standard transformed Gibbs energy of formation(kJ/mol)
%
% OPTIONAL INPUT:
%    adjustedMetList:
% OUTPUT:
%    model:              structure with field:
%
%                          * .met(m).dGft0 - Standard transformed Gibbs energies of reactants
%                            with the baseline adjusted for certain paired
%                            cofactors e.g. `fad` & `fadh2`, such that the
%                            difference between the two is the same as in
%                            Albertys data but the absolute values are
%                            consistent with the group contribution data
%
% .. Author: - Ronan M.T. Fleming
%
% .. Here is the list of cofactors in iAF1260 that have thermodynamic
%    properties backcalculated from Equilibrium values by Alberty
%    The compartments here are important since the same reactant in different
%    compartments may have different properties.

if ~exist('adjustedMetList','var')
%This list of cofactors contains metabolites with own baselines reported
%by Alberty in his 2006 book, plus some metabolites that appear on the
%other side of a reaction from Alberty's set. It is important to keep an
%eye on the latter as they must all be on a common baseline when the
%adjustment is done.
adjustedMetList={'coa','aacoa','accoa','ppcoa','mmcoa_R','mmcoa-R',...
    'succoa','gthrd','gthox','q8h2','q8','fmn','fmnh2','nad',...
    'nadh','nmn','fad','fadh2','nadp','nadph','malcoa'};
end

numChar=1;
[allMetCompartments,uniqueCompartments]=getCompartment(model.mets,numChar);

for p=1:length(uniqueCompartments)

    n=1;
    leftAll{n}=['fad' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['fadh2' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['q8' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['q8h2' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['nad' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['nadh' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['nad' '[' uniqueCompartments{p} ']']; % it is essential that the nad nmn pair are in this position
    rightAll{n}=['nmn' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['nadp' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['nadph' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['gthox' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['gthrd' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['fmn' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['fmnh2' '[' uniqueCompartments{p} ']'];
    n=n+1;
    %here is the list of cofactors in E. coli which are covered by Albertys data
    %see:
    %Standard transformed Gibbs energies of coenzyme A derivatives as
    %functions of pH and ionic strength
    %R.A. Alberty / Biophysical Chemistry 104 (2003) 327�334
    coAset={['coa[' uniqueCompartments{p} ']'],...
            ['aacoa[' uniqueCompartments{p} ']'],...
            ['accoa[' uniqueCompartments{p} ']'],...
            ['mmcoa-R[' uniqueCompartments{p} ']'],...
            ['mmcoa-S[' uniqueCompartments{p} ']'],...
            ['ppcoa[' uniqueCompartments{p} ']'],...
            ['succoa[' uniqueCompartments{p} ']'],...
            ['malcoa[' uniqueCompartments{p} ']']};
    leftAll{n}=[];n=n+1;
    leftAll{n}=['ppcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['succoa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['ppcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['mmcoa-S' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['ppcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=[];n=n+1;
    leftAll{n}=['succoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['succoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['mmcoa-R' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['malcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['malcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['accoa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=[];n=n+1;
    leftAll{n}=['dpcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['dpcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['2tpr3dpcoa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['dpcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['pan4p' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=[];n=n+1;
    leftAll{n}=['sbzcoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['aacoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    % Note this reaction that contains three cofactors:
    % KAT1, 3-ketoacyl-CoA thiolase [c] : aacoa + coa --> (2) accoa
    leftAll{n}=['accoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['coa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    leftAll{n}=['accoa' '[' uniqueCompartments{p} ']'];
    rightAll{n}=['aacoa' '[' uniqueCompartments{p} ']'];
    n=n+1;
    %check 'mmcoa-R'
    % methylmalonyl-CoA epimerase
    % equation:	[c] : mmcoa-R <==> mmcoa-S

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    [nMet,nRxn]=size(model.S);

    exceptionMetBool=false(nMet,1);
    for m=1:nMet
        abbr=model.mets{m};
        abbr=abbr(1:end-3);
        if any(strcmp(abbr,adjustedMetList))
            exceptionMetBool(m)=1;
        end
    end
    fprintf('\n%s\n%s\n','Setting certain Alberty reactants to have a common thermodynamic',...
        'baseline so that this data can be incorporated with group contribution data.')
    fprintf('%s\n','Before baseline edit of Cofactors:')
    fprintf('%s\n','Metabolite properties:')
    %print out exceptional metabolite data before edit to baseline
    fprintf('%10s\t%20s\t%s\t\t%s\t\t%s\n','abbr','albertyAbbr','dGft0Keq','dGft0gc','dGft0');
    for m=1:nMet
        if exceptionMetBool(m)
            fprintf('%10s\t%20s\t%8.4g\t\t%8.4g\t\t%8.4g\n',model.mets{m},model.met(m).albertyAbbreviation,model.met(m).dGft0Keq,model.met(m).dGft0GroupCont,model.met(m).dGft0);
        end
    end
    fprintf('\n\n')

    fprintf('\n\n');
    fprintf('%s\n','Differences between pairs of cofactors (cytoplasm only):')
    fprintf('%22s%10s%10s%10s%10s%10s%16s%16s\n','left-right','#RxnLeft','#RxnRight','#Both','#LNotR','#RNotL','dGtr0Keq','dGrt0');
    for x=1:length(leftAll)
        abbrL=leftAll{x};
        abbrL=[abbrL(1:end-3) '[c]'];
        abbrR=rightAll{x};
        abbrR=[abbrR(1:end-3) '[c]'];

        nCoFcLRxn=length(find(model.S(strcmp(model.mets,abbrL),:)~=0));
        nCoFcRRxn=length(find(model.S(strcmp(model.mets,abbrR),:)~=0));
        nCoFc2Rxn=length(intersect(find(model.S(strcmp(model.mets,abbrL),:)~=0),find(model.S(strcmp(model.mets,abbrR),:)~=0)));
        nCoLeftOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrL),:)~=0),find(model.S(strcmp(model.mets,abbrR),:)~=0)));
        nCoRightOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrR),:)~=0),find(model.S(strcmp(model.mets,abbrL),:)~=0)));

        %nothing to print out if no match
        if any(strcmp(model.mets,abbrL))  && any(strcmp(model.mets,abbrR))
            fprintf('%22s%10i%10i%10i%10i%10i\t%8.4g\t%8.4g\n',[abbrL '-' abbrR],nCoFcLRxn,nCoFcRRxn,nCoFc2Rxn,nCoLeftOnly,nCoRightOnly,...
                model.met(strcmp(model.mets,abbrL)).dGft0Keq-model.met(strcmp(model.mets,abbrR)).dGft0Keq,...
                model.met(strcmp(model.mets,abbrL)).dGft0-model.met(strcmp(model.mets,abbrR)).dGft0);
        end
    end

%     fprintf('%s\n','Differences between pairs of cofactors:')
%     fprintf('%20s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n','left-right','#RxnLeft','#RxnRight','#Both','#LNotR','#RNotL','dGt0Keq','dGt0gc');
%     xx=0;
%     for x=1:length(leftAll)
%         if isempty(leftAll{x})
%             %         fprintf('\n');
%         else
%             nCoFcLRxn=length(find(model.S(strcmp(model.mets,leftAll{x}),:)~=0));
%             nCoFcRRxn=length(find(model.S(strcmp(model.mets,rightAll{x}),:)~=0));
%             nCoFc2Rxn=length(intersect(find(model.S(strcmp(model.mets,leftAll{x}),:)~=0),find(model.S(strcmp(model.mets,rightAll{x}),:)~=0)));
%             nCoLeftOnly=length(setdiff(find(model.S(strcmp(model.mets,leftAll{x}),:)~=0),find(model.S(strcmp(model.mets,rightAll{x}),:)~=0)));
%             nCoRightOnly=length(setdiff(find(model.S(strcmp(model.mets,rightAll{x}),:)~=0),find(model.S(strcmp(model.mets,leftAll{x}),:)~=0)));
%
%             %nothing to print out if no match
%             if any(strcmp(model.mets,leftAll{x}))  && any(strcmp(model.mets,rightAll{x}))
%                 fprintf('%20s\t\t%i\t\t\t%i\t\t%i\t\t%i\t\t%i\t\t%8.4g\t\t%8.4g\n',[leftAll{x} '-' rightAll{x}],nCoFcLRxn,nCoFcRRxn,nCoFc2Rxn,nCoLeftOnly,nCoRightOnly,...
%                     model.met(strcmp(model.mets,leftAll{x})).dGft0Keq-model.met(strcmp(model.mets,rightAll{x})).dGft0Keq,...
%                     model.met(strcmp(model.mets,leftAll{x})).dGft0GroupCont-model.met(strcmp(model.mets,rightAll{x})).dGft0GroupCont);
%             end
%             a=1;
%         end
%     end

    %edit the baseline of a cofactor that is not always used in a pair
    %with another cofactor
    coaSorted=0;
    x=1;
    while x<=length(leftAll)
         abbrL=leftAll{x};
         abbrR=rightAll{x};

        pause(eps)
        if strcmp('coa[c]',abbrR)
            pause(eps)
        end
        %check that both left and right cofactors are always used in the network
        %together because there is no need to change if they are always used
        %together on different sides of the reaction
        if any(strcmp(abbrL,model.mets)) && any(strcmp(abbrR,model.mets))
            %check if coa is involved since coa is a special case that is not a
            %simple cofactor pair
            if ~(any(strcmp(abbrL,coAset) | any(strcmp(abbrR,coAset))))
                nCoLeftOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrL),:)~=0),find(model.S(strcmp(model.mets,abbrR),:)~=0)));
                nCoRightOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrR),:)~=0),find(model.S(strcmp(model.mets,abbrL),:)~=0)));
                %identify which one in each cofactor pair is synthesised/used in
                %another reaction, besides just being used in reactions with its
                %paired cofactor
                if nCoLeftOnly==0 && nCoRightOnly~=0
                    %calculate the difference between the group contribution
                    %estimate and Albertys value
                    baseLineChange=model.met(strcmp(model.mets,abbrR)).dGft0GroupCont-model.met(strcmp(model.mets,abbrR)).dGft0Keq;
                    for m=1:nMet
                        abbr=model.mets{m};
                        if any(strcmp(model.mets{m},{abbrL,abbrR}))
                            %only change if we are using Albertys data
                            if strcmp(model.met(m).dGft0Source,'Keq')
                                model.met(m).dGft0=model.met(m).dGft0+baseLineChange;
                            end
                        end
                    end
                end

                if nCoLeftOnly~=0 && nCoRightOnly==0
                    %calculate the difference between the group contribution
                    %estimate and Albertys value for the left cofactor
                    baseLineChange=model.met(strcmp(model.mets,abbrL)).dGft0GroupCont-model.met(strcmp(model.mets,abbrL)).dGft0Keq;
                    for m=1:nMet
                        if any(strcmp(model.mets{m},{abbrL,abbrR})) % change baseline for both cofactors
                            %only change if we are using Albertys data
                            if strcmp(model.met(m).dGft0Source,'Keq')
                                model.met(m).dGft0=model.met(m).dGft0+baseLineChange;
                            end
                        end
                    end
                    if strcmp(abbrL,['nad' '[' uniqueCompartments{p} ']']) && strcmp(abbrR,['nadh' '[' uniqueCompartments{p} ']']) && any(strcmp(model.mets,['nmn' '[' uniqueCompartments{p} ']'] ))
                        fprintf('%s\n',['Assuming Albertys value for nmn is set to nad baseline']);
                        for m=1:nMet
                            if any(strcmp(model.mets{m},['nmn' '[' uniqueCompartments{p} ']']))
                                %only change if we are using Albertys data
                                if strcmp(model.met(m).dGft0Source,'Keq')
                                    model.met(m).dGft0=model.met(m).dGft0+baseLineChange;
                                end
                            end
                        end
                        %ensure that nad baseline is not changed twice due to nad also
                        %appearing as a cofactor with nmn
                        %skip the next pair as it is nad[c] & nmn[c]
                        x=x+1;
                    end
                end

            else
                %for the cofactors associated with coa by Alberty, then set coA to
                %have the same baseline as the group contribution data
                if coaSorted==0
                    pause(eps)
                    for s=1:length(coAset)
                        %Changed to make suitable for multiple compartments May 24th 2011
                        baseLineChange=model.met(strcmp(model.mets,['coa[' uniqueCompartments{p} ']'])).dGft0GroupCont-model.met(strcmp(model.mets,['coa[' uniqueCompartments{p} ']'])).dGft0Keq;
                        for m=1:nMet
                            if strcmp(coAset{s},model.mets{m})
                                %only change if we are using Albertys data
                                if strcmp(model.met(m).dGft0Source,'Keq')
                                    model.met(m).dGft0=model.met(m).dGft0+baseLineChange;
                                end
                            end
                        end
                    end
                    coaSorted=1;
                end
            end
        end
        x=x+1;
    end
    %compartment by compartment
    coASorted=0;
end

fprintf('\n\n')
fprintf('%s\n','After baseline edit of Cofactors:')
fprintf('%s\n','Metabolite properties:')
fprintf('%10s\t%30s\t%s\t%s\t%s\n','abbr','albertyAbbr','dGft0Keq',' dGft0gc','   dGft0');
%print out the new changes
for m=1:nMet
    if strcmp('nad[c]',model.mets{m})
        pause(eps)
    end
    if exceptionMetBool(m)
        fprintf('%10s\t%30s\t%8.4g\t%8.4g\t%8.4g\n',model.mets{m},model.met(m).albertyAbbreviation,model.met(m).dGft0Keq,model.met(m).dGft0GroupCont,model.met(m).dGft0);
    end
end

fprintf('\n\n');
fprintf('%s\n','Differences between pairs of cofactors, after baseline edit (cytoplasm only):')
fprintf('%22s%10s%10s%10s%10s%10s%16s%16s\n','left-right','#RxnLeft','#RxnRight','#Both','#LNotR','#RNotL','dGtr0Keq','dGrt0');
for x=1:length(leftAll)
    abbrL=leftAll{x};
    abbrL=[abbrL(1:end-3) '[c]'];
    abbrR=rightAll{x};
    abbrR=[abbrR(1:end-3) '[c]'];

    nCoFcLRxn=length(find(model.S(strcmp(model.mets,abbrL),:)~=0));
    nCoFcRRxn=length(find(model.S(strcmp(model.mets,abbrR),:)~=0));
    nCoFc2Rxn=length(intersect(find(model.S(strcmp(model.mets,abbrL),:)~=0),find(model.S(strcmp(model.mets,abbrR),:)~=0)));
    nCoLeftOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrL),:)~=0),find(model.S(strcmp(model.mets,abbrR),:)~=0)));
    nCoRightOnly=length(setdiff(find(model.S(strcmp(model.mets,abbrR),:)~=0),find(model.S(strcmp(model.mets,abbrL),:)~=0)));

    %nothing to print out if no match
    if any(strcmp(model.mets,abbrL))  && any(strcmp(model.mets,abbrR))
        fprintf('%22s%10i%10i%10i%10i%10i\t%8.4g\t%8.4g\n',[abbrL '-' abbrR],nCoFcLRxn,nCoFcRRxn,nCoFc2Rxn,nCoLeftOnly,nCoRightOnly,...
            model.met(strcmp(model.mets,abbrL)).dGft0Keq-model.met(strcmp(model.mets,abbrR)).dGft0Keq,...
            model.met(strcmp(model.mets,abbrL)).dGft0-model.met(strcmp(model.mets,abbrR)).dGft0);
    end
end

%OLD CODE -may still be usefull for looking at nullspace of internal
%reactions that are also involve cofactors
% if ~exist('exceptions','var')
%     exceptions={'coa','aacoa','accoa','ppcoa','mmcoa_R','mmcoa-R',...
%         'succoa','gthrd','gthox','q8h2','q8','fmn','fmnh2','nad',...
%         'nadh','fad','fadh2','nadp','nadph','malcoa'};
% end
%
%
% [nMet,nRxn]=size(model.S);
%
% if ~isfield(model,'biomassRxnAbbr')
%     if ~exist('biomassRxnAbbr')
%         fprintf('\n%s\n','...checkObjective');
%         objectiveAbbr=checkObjective(model);
%         fprintf('%s\n',['Asumming objective is ' objectiveAbbr]);
%         model.biomassRxnAbbr=objectiveAbbr;
%     else
%         model.biomassRxnAbbr=biomassRxnAbbr;
%     end
% end
%
% %finds the reactions in the model which export/import from the model
% %boundary
% %e.g. Exchange reactions
% %     Demand reactions
% %     Sink reactions
% model=findSExRxnInd(model);
% %OUTPUT
% % model.SIntRxnInd          indices of internal reactions
% % model.SExRxnInd           indices of boundary reactions
% intRxnBool=false(1,nRxn);
% intRxnBool(model.SIntRxnInd)=1;
%

%
% %extract subnetwork
% modelE=model;
% %reactions
% exceptionRxnBool=logical(sign(sum(abs(model.S(exceptionMetBool,:)),1)));
% %only keep internal reactions involving exceptional metabolites
% modelE.S=modelE.S(:,exceptionRxnBool & intRxnBool);
% modelE.rxns=modelE.rxns(exceptionRxnBool & intRxnBool);
% modelE.lb=modelE.lb(exceptionRxnBool & intRxnBool);
% modelE.ub=modelE.ub(exceptionRxnBool & intRxnBool);
% %metabolites
% keepMetBool=false(nMet,1);
% for m=1:nMet
%     if nnz(modelE.S(m,:))~=0
%         keepMetBool(m,1)=1;
%     end
% end
% modelE.S=modelE.S(keepMetBool,:);
% modelE.mets=modelE.mets(keepMetBool);
%
% exceptionMetBool=false(nMet,1);
% for m=1:nMet
%     abbr=model.mets{m};
%     if any(strcmp(abbr(end-2:end),'[c]'))
%         abbr=abbr(1:end-3);
%         if any(strcmp(abbr,exceptions))
%             exceptionMetBool(m)=1;
%         end
%     end
% end


function Model = buildRxnEquations(Model)
% Takes a COBRA model and creates reaction equations based
% on the metabolites list, `S` matrix and bound information. Returns the
% model with the additinal field `rxnEquations`. Is called by `cleanTmodel`, `compareCbModels`, `readCbTmodel`, `verifyModel`, `addSEEDInfo`.
%
% USAGE:
%
%    Model = buildRxnEquations(Model)
%
% INPUTS:
%    Model:     COBRA format model or `Tmodel`
%
% OUTPUTS:
%    Model:     Model with corrected `rxnEquations` field.
%
% Please cite:
% `Sauls, J. T., & Buescher, J. M. (2014). Assimilating genome-scale
% metabolic reconstructions with modelBorgifier. Bioinformatics
% (Oxford, England), 30(7), 1036?8`. http://doi.org/10.1093/bioinformatics/btt747
%
% ..
%    Edit the above text to modify the response to help addMetInfo
%    Last Modified by GUIDE v2.5 06-Dec-2013 14:19:28
%    This file is published under Creative Commons BY-NC-SA.
%
%    Correspondance:
%    johntsauls@gmail.com
%
%    Developed at:
%    BRAIN Aktiengesellschaft
%    Microbial Production Technologies Unit
%    Quantitative Biology and Sequencing Platform
%    Darmstaeter Str. 34-36
%    64673 Zwingenberg, Germany
%    www.brain-biotech.de

if isstruct(Model.lb) % If the function is fed Tmodel, use reaction bounds from first model.
    modelNames = fieldnames(Model.Models) ;
    firstModel = modelNames{1} ;
    lb = Model.lb.(firstModel) ;
    ub = Model.ub.(firstModel) ;
else
    lb = Model.lb ;
    ub = Model.ub ;
end

%% Make the equations.
for iRxn = 1:length(Model.rxns)
    rxnCode = [] ;

    educts   = find(Model.S(:,iRxn) < 0) ;
    products = find(Model.S(:,iRxn) > 0) ;
    if lb(iRxn) < 0 && ub(iRxn) > 0
        arrow = '<==>' ;
    elseif lb(iRxn) >= 0 && ub(iRxn) > 0
        arrow = '-->' ;
    elseif lb(iRxn) < 0 && ub(iRxn) <= 0
        arrow = '<--' ;
    elseif lb(iRxn) == 0 && ub(iRxn) == 0
        arrow = '|||' ;
    else
        disp(['Do not understand directionality of ' Model.rxns{iRxn}])
    end

    if ~isempty(educts)
        for ie = 1:length(educts)
            if Model.S(educts(ie), iRxn) ~= -1
                rxnCode = [rxnCode '(' num2str(-Model.S(educts(ie),iRxn)) ...
                          ') ' Model.mets{educts(ie)} ] ;
            else
                rxnCode = [rxnCode Model.mets{educts(ie)}] ;
            end
            rxnCode = [rxnCode ' + '] ;
        end
        % Note how just 2 positions are substracted from the end, leaving a
        % space.
        rxnCode = [rxnCode(1:end - 2) arrow] ;
    end
    if ~isempty(products)
        rxnCode = [rxnCode ' '] ;
        for ip = 1:length(products)
            if Model.S(products(ip), iRxn) ~= 1
                rxnCode = [rxnCode '(' num2str(Model.S(products(ip), iRxn))...
                          ') ' Model.mets{products(ip)}] ;
            else
                rxnCode = [rxnCode Model.mets{products(ip)}] ;
            end
            rxnCode = [rxnCode ' + '] ;
        end
        rxnCode = rxnCode(1:end - 3) ;
    end

    Model.rxnEquations{iRxn, 1} = rxnCode ;
    clear educts ; clear products; clear ie ; clear ip ; clear rxcode ;
end


function result = parseBC(N, BC, type)
%PARSEBC  Parse boundary conditions for CHEBOP object.
%   This method is not intended for end users. For information about boundary
%   conditions in CHEBOPs, see CHEBOP.
%
%   This method is invoked by the set methods for LBC, RBC, and BC. The types
%   and meanings of the allowed input are described in the documentation for
%   CHEBOP. The result is either empty or a function handle that represents the
%   given condition as needed internally.

% Copyright 2017 by The University of Oxford and The Chebfun Developers.
% See http://www.chebfun.org/ for Chebfun information.

numIn = nargin(N);

if ( isempty(BC) )
    result = [];

elseif ( isnumeric(BC) )
    % We reach here if we get assigned BCs like N.lbc = [1; 3].
    %   * In the scalar case, this means setting up conditions so that the
    %     solution equals the first entry of the vector at the endpoint, its
    %     derivative the second entry etc. In the example above, for a
    %     second order problem on [0,1], this amounts to
    %         u(0) = 1, u'(0) = 3.
    %   * In the system case, this means that the value of the first unknown
    %     function equals the first entry of the vector, the second unknown
    %     function the second entry of the vector, etc. In the example above,
    %     for a coupled system on [0,1], this amounts to
    %         u(0) = 1, v(0) = 3.
    %     Higher order coupled systems are not supported, as that would require
    %     inspecting the diffOrder of the linearized chebop, a (relatively)
    %     computionally expensive operation. That information only becomes
    %     available for free once we've called \ for starting to solve the
    %     problem.
    result = @(varargin) setupNumericalConditions(BC, varargin);

elseif ( isa(BC, 'function_handle') )
    % If we are dealing with a scalar problem where the independent variable is
    % not specified in the function handle arguments, allow also passing an
    % input function handle that takes one argument. Otherwise, we request that
    % the number of input to the BC function handle is one less than the number
    % of arguments to the OP part. A special case is when BCs are specified as a
    % vector, this leads to nargin(BC) = -1 (since we assigned the boundary
    % conditions as an anonymous function with a varargin argument), if this is
    % the case and the number of inputs for the BC don't match the operator, a
    % meaningful error will be thrown later on.
    if ( (numIn == 0) || ( (numIn == 1) && (nargin(BC) == 1) ) || ...
            ( strcmp(type,'lrbc') && (nargin(BC) == (numIn - 1)) ) || ...
            ( strcmp(type,'lrbc') && (nargin(BC) == -1) ) || ...
            ( strcmp(type,'bc') && (nargin(BC) == numIn) ) || ...
            ( strcmp(type,'bc') && (nargin(BC) == numIn + 1) ) )
        % We've got an anonymous function we're happy with. Do we need to
        % vectorize it?
        if ( N.vectorize && ~strcmp(func2str(BC), vectorize(BC)) )
            result = N.vectorizeOp(BC);
        else
            result = BC;
        end
    else
        error('CHEBFUN:CHEBOP:parseBC:inputs', ...
            'Number of inputs to BCs do not match operator.');
    end

elseif ( strcmpi(BC, 'neumann') )
    % Homogeneous Neumann.
    if ( numIn <= 2 )
        result = @(u) diff(u);
    else
        error('CHEBFUN:CHEBOP:parseBC:neuman', ...
            'Can only assign scalar BCs to scalar problems.');
    end

elseif ( strcmpi(BC, 'dirichlet') )
    % Homogeneous Dirichlet.
    if ( numIn <= 2 )
        result = @(u) u;
    else
        error('CHEBFUN:CHEBOP:parseBC:dirichlet', ...
            'Can only assign scalar BCs to scalar problems.');
    end

elseif ( iscell(BC) ) && ( length(BC) == 2 ) && ( ischar(BC{2}) ) && ...
        ( isnumeric(BC{1}) )
    % A cell may have a numerical value followed by a keyword of 'dirichlet' or
    % 'neumann'.

    % This behavior is retained only for backward compatibility and only for
    % problems with one variable.
    warning('CHEBFUN:CHEBOP:parseBC:keywordbc',...
        ['Keyword/value specifications of boundary conditions are ', ...
         'deprecated and may be removed in future versions of Chebfun.'])

    if ( strcmpi(BC{2}, 'neumann') && ( numIn <= 2 ) )
        result = @(u) diff(u) - BC{1};
    elseif ( strcmpi(BC{2}, 'dirichlet') && ( numIn <= 2 ) )
        result = @(u) u - BC{1};
    else
         error('CHEBFUN:CHEBOP:parseBC:cell', ...
            'Unable to parse cell input to set BC.');
    end

else
    error('CHEBFUN:CHEBOP:parseBC:unknown', 'Unsupported format of BCs.')

end

end

function out = setupNumericalConditions(BC, varargin)
% SETUPNUMERICALCONDITIONS  Return anonymous function for evaluating BCs.
%
% We need this function so that we can construct a function handle that allows
% specifying boundary conditions with a vector. In the scalar case, we will be
% subtracting the nth entry of the BC vector from the (n-1)st derivative of the
% solution. In the system case, we will be subtracting the nth entry of the
% vector from the nth unknown function at the endpoint:

u = varargin{1};
% Check if we're dealing with scalar or system case:
if (length(u) == 1)
    % Scalar case, subtract entries of BC from U and its derivatives:
    u = u{1};
    % We must always have at least one condition
    out = u - BC(1);
    for bcCounter = 2:length(BC)
        % Add to the boundary conditions vector. It's tricky to initialize the
        % OUT argument to the correct dimensions, as we need to be able to
        % evaluate this both with CHEBFUNs, ADCHEBFUNs and TREEVARs, hence a
        % cheeky growth of the array.
        out = [out; diff(u, bcCounter-1) - BC(bcCounter)]; %#ok<AGROW>
    end
else
    assert(length(u) == length(BC), ...
        'CHEBFUN:CHEBOP:parseBC:numberOfConditions', ...
        ['The number of initial/final conditions appears to be greater than' ...
        ' the number of variables in problem. Specifying boundary ' ...
        'conditions as a vector for systems is only supported for first ' ...
        'order systems.']);
    % We must always have at least one condition
    out = u{1} - BC(1);
    for bcCounter = 2:length(BC)
        % Add to the boundary conditions vector. Like above, it's tricky to
        % initialize the OUT argument to the correct dimensions, as we need to
        % be able to evaluate this both with CHEBFUNs, ADCHEBFUNs and TREEVARs.
        % So, allow ourselves to grow the array, by subtracting elements from
        % the BC vector from the solution components.
        out = [out; u{bcCounter} - BC(bcCounter)]; %#ok<AGROW>
    end
end

end