function model = ml_traincov(varargin)
% Learn a linear predictive model using covariance-based classification.
% Model = ml_traincov(Trials, Targets, Options...)
%
% This method assumes that the given trials values represent covariance matrices and offers various
% methods, including information geometric approaches, to classify them. This implementation uses
% the covariance toolbox by Alexandre Barachant.
%
% In:
%   Trials       : training data, as in ml_train
%
%   Targets      : target variable, as in ml_train
%
% Out:
%   Model   : the predictive model; can be used with ml_predictcov
%
% Examples:
%   % learn a model using the defaults
%   model = ml_traincov(trials,targets);
%
% See also:
%   ml_predictcov
%
%                           Christian Kothe, Syntrogi
%                           2015-04-21
dp;

arg_define([0 3],varargin, ...
    arg_norep('trials'), ...
    arg_norep('targets'), ...
    arg({'classifier','ClassificationMethod'},'mdm',{'mdm','fgmdm','tslda'}, 'Classification method to use. The mdm method (minimum distance to mean) is based on minimum distance to class mean using a given distance metric and mean estimation metric. The fgmdm (filtered geodesic mdm) method additionally performs geodesic filtering. The tslda method (tangent-space linear discriminant analysis) is an LDA implementation that respects the Riemannian geometry of the data.'), ...
    arg({'mean_est','MeanEstimator'},'riemann',{'arithmetic','riemann','riemanndiag','riemanntrim','median','riemannmed','logeuclid','opttransp','ld','geodesic','harmonic','geometric'},'Method to average covariance matrices. Various methods are supported, including Riemannian mean/median/trimmed mean, log-euclidean mean, optimal transportation mean, log determinant mean, and geodesic iterative mean.'), ...
    arg({'distance_metric','DistanceMetric'},'riemann',{'euclid','riemann','kullback','logeuclid','opttransp','ld'},'Distance metric to use. This is only used for the mdm and fgmdm methods. Different distance metrics are supported, including the Euclidean metric, the Riemannian distance, Kullback-Leibler divergence, log-euclidean distance, optimal transportation distance, and log determinant distance'));

% find the class labels
classes = unique(targets);
if length(classes) == 1
    error('BCILAB:only_one_class','Your training data set has no trials for one of your classes; you need at least two classes to train a classifier.\n\nThe most likely reasons are that one of your target markers does not occur in the data, or that all your trials of a particular class are concentrated in a single short segment of your data (10 or 20 percent). The latter would be a problem with the experiment design.');
else
    % reformat feature shape to DxDxN
    if ndims(trials) == 2 %#ok<ISMAT,NODEF>
        [N,F] = size(trials);
        D = sqrt(F);
        if abs(D-round(D)) > 0
            error('The number of features in your trials must be a square of an integer.'); end
        trials = reshape(trials',[D,D,N]);
    else
        [U,V,N] = size(trials); %#ok<ASGLU>
        if U ~= V
            error('Your feature matrices are not square, i.e., cannot be covariance matrices.'); end
    end

    model.classes = classes;
    switch classifier
        case 'mdm'
            % minimum distance to mean: estimate class means
            for c=length(classes):-1:1
                model.C{c} = mean_covariances(trials(:,:,targets==classes(c)),mean_est); end
        case 'fgmdm'
            % filtered geodesic minimum distance to mean
            % geodesic filtering
            [model.W,model.Cg] = fgda(trials,targets,mean_est,{},'shcov',{});
            trials = geodesic_filter(trials,model.Cg,model.W(:,1:length(model.classes)-1));
            % estimate class means
            for c=length(classes):-1:1
                model.C{c} = mean_covariances(trials(:,:,targets==classes(c)),mean_est); end
        case 'tslda'
            error('This variant is not yet implemented!');
        otherwise
            error('Unsupported classification method: %s',hlp_tostring(classifier));
    end
end

model.classifier = classifier;
model.mean_est = mean_est;
model.distance_metric = distance_metric;


function [fx,dfdx,dfdp] = f_HRF2(Xt,P,ut,in)
% Balloon (HRF) model evolution function in log-space
% function [fx,dfdx,dfdp] = f_HRF2(Xt,P,ut,in)
% This function evaluates the evolution function derived from the balloon
% model for the hemodynamic response function. It can be called in two
% ways: (i) as a "stand-alone" evolution function, whereby the system's
% states are hemodynamic states of the balloon model, or (ii) as a
% generalized observation function, where the real system's states are the
% neuronal states of a DCM for fMRI model. Note that the hemodynamic states
% are in log-space, for positivity constrints.

deltat = in.deltat;
n = size(Xt,1);

% Get parameters
[E0,V0,tau0,kaf,kas,epsilon,alpha] = BOLD_parameters;
if isfield(in,'fullDCM') && in.fullDCM
    nreg = n./5;
    ind1 = in.ind1;
    ind2 = in.ind2;
    ind3 = in.ind3;
    ind4 = in.ind4;
    n1 = in.n1;
    n2 = in.n2;
    n3 = in.n3;
    n4 = in.n4;
    try, n5=in.n5; catch, n5=[];end
    epsilon = 1;
    alpha = alpha.*exp(P(in.ind5));
else
    nreg = n./4;
    ind1 = 1;
    ind2 = 2;
    ind3 = 3;
    ind4 = 4;
    n1 = 1;
    n2 = 2;
    n3 = 3;
    n4 = 4;
    epsilon = epsilon.*exp(P(5));
    alpha = alpha.*exp(P(6));
end
% [E0,dsdp] = sigm(P(ind1)-0.6633,struct('beta',1,'G0',1,'INV',0));
% E0 = E0(:);
E0 = 1./(1+exp(-(P(ind1)-0.6633)));
dsdp = E0.*(1-E0);
tau0 = tau0.*exp(P(ind2));
kaf = kaf.*exp(P(ind3));
kas = kas.*exp(P(ind4));

if isfield(in,'xshift') % for numerical stability
    xshift = in.xshift;
else
    xshift = 0;
end

% hemodynamic states ...
if isfield(in,'linearized') && in.linearized
    x1 = zeros(nreg,1);
    x2 = ones(nreg,1);
    x3 = ones(nreg,1);
    x4 = ones(nreg,1);
else
    % vasodilatory signal s(t)
    x1 = Xt(n1,:);
    % blood inflow f(t)
    if isfield(in,'logx2') && ~in.logx2
        x2 = Xt(n2,:) + 1; % deviation to steady-state!
    else
        x2 = exp(Xt(n2,:)) + xshift;
    end
    % blood volume v(t)
    x3 = exp(Xt(n3,:)) + 0.*xshift;
    % dHb content q(t)
    x4 = exp(Xt(n4,:)) + 0.*xshift;
end
% blood outflow
fv = x3.^(1./alpha);
% d[blood flow]/dXt(3)
dfvdx = (1./alpha).*fv;
% oxygen extraction
ff = (1-(1-E0).^(1./x2))./E0;
% d[O2 extraction]/dXt(2)
if isfield(in,'logx2') && ~in.logx2
    dffdx = log(1-E0).*(1-E0).^(1./x2)./(E0.*x2.^2);
else
    dffdx = log(1-E0).*(1-E0).^(1./x2)./(E0.*x2);
end
% ... and flow field, derivatives, etc...
f = zeros(n,1);
J = zeros(n,n);
dfdp = zeros(size(P,1),n);

% Evaluate flow field
f(n1) = (epsilon.*ut - kas.*x1 - kaf.*(x2 - 1));
if isfield(in,'logx2') && ~in.logx2
    f(n2) = x1;
else
    f(n2) = x1./x2;
end
f(n3) = (x2 - fv)./(tau0.*x3);
f(n4) = (x2.*ff./x4 - fv./x3)./tau0;

% Evaluate jacobian and gradients wrt parameters
for i=1:nreg

    if isfield(in,'logx2') && ~in.logx2
        J(n1(i),n1(i):n1(i)+3) = [ -kas(i) , 1, 0, 0 ];
        J(n2(i),n1(i):n1(i)+3) = [ -kaf(i), 0, ...
            1./(tau0(i).*x3(i)), ...
            (ff(i)+x2(i).*dffdx(i))./(x4(i).*tau0(i))];
    else
        J(n1(i),n1(i):n1(i)+3) = [ -kas(i) , 1./x2(i), 0, 0 ];
        J(n2(i),n1(i):n1(i)+3) = [ -kaf(i).*x2(i), -x1(i)./x2(i), ...
            x2(i)./(tau0(i).*x3(i)), ...
            x2(i).*(ff(i)+dffdx(i))./(tau0(i).*x4(i))];
    end
    J(n3(i),n1(i):n1(i)+3) = [ 0, 0,...
        -f(n3(i)) - dfvdx(i)./(tau0(i).*x3(i)) , ...
        (fv(i)-dfvdx(i))./(tau0(i).*x3(i))];
    J(n4(i),n1(i):n1(i)+3) = [ 0, 0, 0, ...
        -(x2(i).*ff(i))./(tau0(i).*x4(i)) ];

    if isfield(in,'linearized') && in.linearized

        tmp = log(1-E0(i))./E0(i);

        dfdp(ind1(i),n1(i):n1(i)+3) = [0,0,0,...
            -dsdp(i).*(1+tmp).*Xt(n2(i))./(tau0(i).*E0(i))];
        dfdp(ind2(i),n1(i):n1(i)+3) = [0,0,...
            (-Xt(n2(i))+Xt(n3(i))./alpha(i))./tau0(i),...
            -((ff(i)+dffdx(i)).*Xt(n2(i)) + (fv(i)-dfvdx(i)).*Xt(n3(i)) ...
            - ff(i).*Xt(n4(i)))./tau0(i)];
        dfdp(ind3(i),n1(i):n1(i)+3) = kaf(i).*[-Xt(n2(i)),0,0,0];
        dfdp(ind4(i),n1(i):n1(i)+3) = kas(i).*[-Xt(n1(i)),0,0,0];

        if isfield(in,'fullDCM') && in.fullDCM
            if  ~isempty(n5)
                J(n5(i),n1(i)) = epsilon;
            end
            dfdp(in.ind5(i),n1(i):n1(i)+3) = [0,0,...
                Xt(n3(i))./(tau0(i).*alpha(i)),...
                Xt(n3(i))./(tau0(i).*alpha(i))];
        else
            dfdp(5,:) = epsilon.*[ut,0,0,0];
            dfdp(6,:) = [0,0,...
                Xt(n3(i))./(tau0(i).*alpha(i)),...
                Xt(n3(i))./(tau0(i).*alpha(i))];
        end

    else

        % gradient wrt parameters
        dfdp(ind1(i),n1(i):n1(i)+3) = [0,0,0,...
            (((1-E0(i)).^(-1+1./x2(i)))-x2(i).*ff(i))...
            .*dsdp(i)./(tau0(i).*x4(i).*E0(i))];
        dfdp(ind2(i),n1(i):n1(i)+3) = [0,0,...
            -(x2(i) - fv(i))./(tau0(i).*x3(i)),...
            -(x2(i).*ff(i)./x4(i) - fv(i)./x3(i))./tau0(i)];
        dfdp(ind3(i),n1(i):n1(i)+3) = kaf(i).*[-x2(i)+1,0,0,0];
        dfdp(ind4(i),n1(i):n1(i)+3) = kas(i).*[-x1(i),0,0,0];

        % complement gradients if used for full DCM model
        if isfield(in,'fullDCM') && in.fullDCM
            if  ~isempty(n5)
                J(n5(i),n1(i)) = epsilon;
            end
            dfdp(in.ind5(i),n1(i):n1(i)+3) = [0,0,...
                log(x3(i)).*fv(i)./(tau0(i).*x3(i).*alpha(i)),...
                log(x3(i)).*fv(i)./(tau0(i).*x3(i).*alpha(i))];
        else
            dfdp(5,:) = epsilon.*[ut,0,0,0];
            dfdp(6,:) = [0,0,...
                log(x3).*fv./(tau0.*x3.*alpha),...
                log(x3).*fv./(tau0.*x3.*alpha)];
        end

    end

end

% Apply Euler discretization
if isfield(in,'linearized') && in.linearized
     if isfield(in,'fullDCM') && in.fullDCM && ~isempty(n5)
         Cu = 0;
     else
         Cu = zeros(n,1);
         Cu(n1) = epsilon.*ut;
     end
    fx = Xt + deltat.*(J'*Xt + Cu);
else
    fx = Xt + deltat.*f;
end
dfdp = deltat.*dfdp;
dfdx = eye(n) + deltat.*J;


function [ eigenvalues, kpoints, nelect ] = import_eigenval( filename )
%IMPORT_EIGENVAL Import a VASP EIGENVAL file.
%   [eigenvalues,kpoints,nelect] = IMPORT_EIGENVAL(filename) imports a VASP
%   EIGENVAL file. Optional parameter filename specifies the name of the
%   file. eigenvalues is a NKPOINTS x NBANDS x ISPIN array of eigenvalues,
%   kpoints is a NKPOINTS x 4 array of k-point coordinates and weights, and
%   nelect is the number of electrons.

% todo:
% check compatibility with non-spin-polarized files

  if nargin == 0
      filename='EIGENVAL';
  end

  fid = fopen(filename);
  if fid==-1
    error(['File ' filename ' not found']);
  end

  buffer = fscanf(fid, '%d', 4); % various data
  ispin = buffer(4); % 1 = non-polarized, 2 = polarized
  fgetl(fid); % empty string
  fgetl(fid); % various data
  fgetl(fid); % various data
  fgetl(fid); % various data
  fgetl(fid); % comment
  nelect = fscanf(fid, '%d', 1); % number of electrons
  nkpoints = fscanf(fid, '%d', 1); % number of k-points
  nbands = fscanf(fid, '%d', 1); % number of bands

  fgetl(fid); % empty string

  kpoints = zeros(nkpoints,4);
  eigenvalues = zeros(nkpoints, nbands, ispin);

  for kpoint = 1:nkpoints
    fgetl(fid); % blank line
    kpoints(kpoint,:) = fscanf(fid, '%f', 4)';
    fgetl(fid); % empty string
    for band = 1:nbands
        buffer = fscanf(fid,'%f',ispin+1);
        eigenvalues(kpoint,band,1:ispin) = buffer(2:(ispin+1));
        fgetl(fid); % empty string
    end
  end

  eigenvalues = sort(eigenvalues,2); % sort the bands by energy

end


function [mpe, ll] = calc_mpe_bucket(bnet, new_evidence, max_over)
%
% PURPOSE:
%       CALC_MPE Computes the most probable explanation to the network nodes
%       given the evidence.
%
%       [mpe, ll] = calc_mpe(engine, new_evidence, max_over)
%
% INPUT:
%       bnet  - the bayesian network
%       new_evidence - optional, if specified - evidence to be incorporated [cell(1,n)]
%       max_over - optional, if specified determines the variable elimination order [1:n]
%
% OUTPUT:
%       mpe - the MPE assignmet for the net variables (or [] if no satisfying assignment)
%       ll - log assignment probability.
%
% Notes:
% 1. Adapted from '@var_elim_inf_engine\marginal_nodes' for MPE by Ron Zohar, 8/7/01
% 2. Only discrete potentials are supported at this time.
% 3. Complexity: O(nw*) where n is the number of nodes and w* is the induced tree width.
% 4. Implementation based on:
%  - R. Dechter, "Bucket Elimination: A Unifying Framework for Probabilistic Inference",
%                 UA1 96, pp. 211-219.

ns = bnet.node_sizes;
n = length(bnet.dag);
evidence = cell(1,n);
if (nargin<2)
    new_evidence = evidence;
end

onodes = find(~isemptycell(new_evidence));  % observed nodes
hnodes = find(isemptycell(new_evidence));  % hidden nodes
pot_type = determine_pot_type(bnet, onodes);

if pot_type ~= 'd'
  error('only disrete potentials supported at this time')
end

for i=1:n
  fam = family(bnet.dag, i);
  CPT{i} = convert_to_pot(bnet.CPD{bnet.equiv_class(i)}, pot_type, fam(:), evidence);
end

% handle observed nodes: set impossible cases' probability to zero
% rather than prun matrix (this makes backtracking easier)

for ii=onodes
  lIdx = 1:ns(ii);
  lIdx = setdiff(lIdx, new_evidence{ii});

  sCPT=struct(CPT{ii});  % violate object privacy

  sargs = '';
  for jj=1:(length(sCPT.domain)-1)
    sargs = [sargs, ':,'];
  end
  for jj=lIdx
    eval(['sCPT.T(', sargs, num2str(jj), ')=0;']);
  end
  CPT{ii}=dpot(sCPT.domain, sCPT.sizes, sCPT.T);
end

B = cell(1,n);
for b=1:n
  B{b} = mk_initial_pot(pot_type, [], [], [], []);
end

if (nargin<3)
  max_over = (1:n);
end
order = max_over; % no attempt to optimize this

% Initialize the buckets with the CPDs assigned to them
for i=1:n
  b = bucket_num(domain_pot(CPT{i}), order);
  B{b} = multiply_pots(B{b}, CPT{i});
end

% Do backward phase
max_over = max_over(length(max_over):-1:1); % reverse
for i=max_over(1:end-1)
  % max-ing over variable i which occurs in bucket j
  j = bucket_num(i, order);
  rest = mysetdiff(domain_pot(B{j}), i);
  %temp = marginalize_pot_max(B{j}, rest);
  temp = marginalize_pot(B{j}, rest, 1);
  b = bucket_num(domain_pot(temp), order);
  %        fprintf('maxing over bucket %d (var %d), putting result into bucket %d\n', j, i, b);
  sB=struct(B{b});  % violate object privacy
  if ~isempty(sB.domain)
    B{b} = multiply_pots(B{b}, temp);
  else
    B{b} = temp;
  end
end
result = B{1};
marginal = pot_to_marginal(result);
[prob, mpe] = max(marginal.T);

% handle impossible cases
if ~(prob>0)
  mpe = [];
  ll = -inf;
  %warning('evidence has zero probability')
  return
end

ll = log(prob);

% Do forward phase
for ii=2:n
  marginal = pot_to_marginal(B{ii});
  mpeidx = [];
  for jj=order(1:length(mpe))
    assert(ismember(jj, marginal.domain)) %%% bug
    temp = find_equiv_posns(jj, marginal.domain);
    mpeidx = [mpeidx, temp] ;
    if isempty(temp)
      mpeidx = [mpeidx, Inf] ;
    end
  end
  [mpeidxsorted sortedtompe] = sort(mpeidx) ;

  % maximize the matrix obtained from assigning values from previous buckets.
  % this is done by building a string and using eval.

  kk=1;
  sargs = '(';
  for jj=1:length(marginal.domain)
    if (jj~=1)
      sargs = [sargs, ','];
    end
    if (mpeidxsorted(kk)==jj)
      sargs = [sargs, num2str(mpe(sortedtompe(kk)))];
      if (kk<length(mpe))
	kk = kk+1 ;
      end
    else
      sargs = [sargs, ':'];
    end
  end
  sargs = [sargs, ')'] ;
  eval(['[val, loc] = max(marginal.T', sargs, ');'])
  mpe = [mpe loc];
end
[I,J] = sort(order);
mpe = mpe(J);

%%%%%%%%%

function b = bucket_num(domain, order)

b = max(find_equiv_posns(domain, order));


function [SGP4Elements,TTEpoch1,TTEpoch2,otherInfo,checksumIsBad]=TLE2SGP4OrbEls(TLELine1,TLELine2)
%%TLE2SGP4ORBELS Convert a North American Aerospace Defense Command (NORAD)
%                two-line element (TLE) satellite ephemerides into a format
%                that can be used with the propagateOrbitSGP4 function for
%                determining the position and velocity of the satellite at
%                different times using the Simplified General
%                Perturbations 4 (SGP4) model/ the Simplified Deep Space
%                Perturbations 4 (SDP4) model for distant satellites.
%                TLEs are how the Air Force Space Command (AFSPC) has
%                publicly released information on satellite orbits for
%                years.
%
%INPUTS: TLELine1, TLELine2 The character strings for each of the two lines
%                           that make up the TLE ephemeride, including all
%                           spaces as the positions of the characters are
%                           important. With checksums, these should both be
%                           69 characters long, without checksums, 68
%                           characters long.
%
%OUTPUTS: SGP4Elements The 7X1 vector of orbital elements that can be given
%                      to the propagateOrbitSGP4 function along with the
%                      epoch time to predict the location of the satellite
%                      at various times. They are similar, but not
%                      identical to, Keplerian orbital elements of the same
%                      name. If either of the TLE strings is too short,
%                      this is an empty matrix. The elements of
%                      SGP4Elements are
%                      1) eccentricity
%                      2) inclination  (radians)
%                      3) argument of perigee (radians)
%                      4) right ascension of the ascending node (radians)
%                      5) mean anomaly (radians)
%                      6) mean motion (radians per second [TT])
%                      7) BSTAR drag term. This pseudo ballistic
%                        coefficient has units of inverse Earth radii.
%                        Normally, a ballistic coefficient BC is mass per
%                        unit area divided by a drag coefficient. The BSTAR
%                        drag term is a scaled version of the normal
%                        ballistic coefficient. That is,
%                        BC=Re*rho_0/(2*BSTAR) where Re is the radius of
%                        the Earth in meters, and rho=2.461e-5 kg/m^2.
%   TTEpoch1, TTEpoch2 The epoch time of the SGP4Elements given in
%                      terrestrial time (TT) as a two-part Julian date.
%                      This will be incorrect from 2057 onward, since the
%                      TLE data only provides the final two digits of the
%                      year starting in 1957 and the century must be
%                      guessed. If either of the TLE strings is too short,
%                      these are empty matrices.
%            otherInfo A structure holding the other information present in
%                      the TLE. If either of the TLE strings is too short,
%                      this is an empty matrix. The fields of this
%                      structure are
%                      line1Number The number starting the first TLE line.
%                                  This should be 1.
%                      line2Number The number starting the second TLE line.
%                                  This should be 2.
%                      satelliteNumber1 The NORAD catalog number of the
%                                  satellite as given by the first line of
%                                  the TLE.
%                      satelliteNumber2 The NORAD catalog number of the
%                                  satellite as given by the second line of
%                                  the TLE. This should be the same as
%                                  satelliteNumber1
%                      classification A character indicating the
%                                  classification level of the satellite.
%                                  'U'=unclassified.
%                      IDLaunchYear The last two digits of the launch year
%                                  of the satellite.
%                      IDLaunchNumber The number of the launch in in the
%                                  launch year that put up the satellite.
%                      IDPieceOfLaunch A string indicating which piece of
%                                  the launch the satellite is.
%                      firstDerivMeanMotion The first derivative of the
%                                  mean motion in radians per second
%                                  squared. This is useful with the older
%                                  orbital propagators.
%                      secondDerivMeanMotion The second derivative of the
%                                  mean motion in radians per second cubed.
%                      ephemerisType Originally, this was a number from
%                                  1-5 indicating the type of orbital
%                                  propagator used to generate the
%                                  ephemeris with 1=SGP, 2=SGP4, 3=SDP4,
%                                  4=SGP8, and 5=SDP8. However, it is
%                                  always set to zero in TLEs that are
%                                  publicly released and is thus
%                                  meaningless.
%                      elementNumber A number indicating which TLE set this
%                                  TLE is from. The number is supposed to
%                                  be incremented everytime a new set comes
%                                  out. However, in practice, it is not.
%                      revolutionNumberAtEpoch The number of orbits the
%                                  satellite has made since its launch.
%        checksumIsBad A 2X1 boolean vector indicating whether the checksum
%                      value for each of the strings is bad.
%                      checksumIsBad(1) is true if the checksum of TLELine1
%                      is invalid and checksumIsBad(2) is true if the
%                      checksum of TLELine2 is invalid. If a checksums is
%                      omitted then the value will be true. If iether of
%                      the strings is too short, then this will be an empty
%                      matrix.
%
%Note that parts of the input strings that cannot be read will be filled
%with NaN values.
%
%Information of the format of the TLE sets can be found at [1]. The
%documentation discussing how the element sets are used is in [2] and [3].
%The decimal point parsing part of this function is loosely based on David
%Vallado's public-domain code for the SGP4 propagator that was downloaded
%from http://www.centerforspace.com/downloads/
%
%A sample TLE that accompanies Vallado's code is:
%TLELine1='1 11801U          80230.29629788  .01431103  00000-0  14311-1      13'
%TLELine2='2 11801  46.7916 230.4354 7318036  47.4722  10.4117  2.28537848    13'
%
%REFERENCES:
%[1] T. C. for Space Standards and Innovation. (2012, 27 Sep.) NORAD two-
%    line element sets. [Online].
%    Available: http://www.celestrak.com/NORAD/elements/
%[2] F. R. Hoots and R. L. Roehrich, "Spacetrack report no. 3: Models for
%    propagation of NORAD element sets," Department of Defense, Tech. Rep.,
%    31 Dec. 1988. [Online].
%    Available: http://www.amsat.org/amsat/ftp/docs/spacetrk.pdf
%[3] D. A. Vallado, P. Crawford, R. Hujsak, and T. S. Kelso, "Revisiting
%    spacetrack report # 3: Rev 2," in Proceedings of the AIAA/AAS
%    Astrodynamics Specialist Conference and Exhibit, Keystone, CO, 21-24
%    Aug. 2006. [Online].
%    Available: http://celestrak.com/publications/AIAA/2006-6753/AIAA-2006-6753.pdf
%
%December 2014 David F. Crouse, Naval Research Laboratory, Washington D.C.*/
%(UNCLASSIFIED) DISTRIBUTION STATEMENT A. Approved for public release.

%Multiplication coefficient to convert from degrees to radians.
deg2Rad=pi/180;

%If either of the strings is too short.
if(length(TLELine1)<68||length(TLELine2)<68)
    SGP4Elements=[];
    TTEpoch1=[];
    TTEpoch2=[];
    otherInfo=[];
    checksumIsBad=[];
    return;
end

%If the strings have no checksums at the end, then just mark the checksums
%as bad.
if(length(TLELine1)<69)
    checksumIsBad(1)=true;
else
    checksumIsBad(1)=~validateTLEChecksum(TLELine1);
end

if(length(TLELine2)<69)
    checksumIsBad(2)=true;
else
    checksumIsBad(2)=~validateTLEChecksum(TLELine2);
end

%Set the implied decimal points to help deal with bad values when doing a
%formatted read.
for j=11:16
    if(TLELine1(j)==' ')
        TLELine1(j)='_';
    end
end

if(TLELine1(45)~=' ')
    TLELine1(44)=TLELine1(45);
end
TLELine1(45)='.';

if(TLELine1(8)==' ')
    TLELine1(8)='U';
end

if(TLELine1(10)==' ')
    TLELine1(10)='.';
end

for j=46:50
    if (TLELine1(j)==' ')
        TLELine1(j)='0';
    end
end

if(TLELine1(52)==' ')
    TLELine1(52)='0';
end

if(TLELine1(54)~=' ')
    TLELine1(53)=TLELine1(54);
end
TLELine1(54)='.';
TLELine2(26)='.';

for j=27:33
    if(TLELine2(j)==' ')
        TLELine2(j)='0';
    end
end

if(TLELine1(63)==' ')
    TLELine1(63)='0';
end

if((length(TLELine1)<68)||(TLELine1(68)==' '))
    TLELine1(68)='0';
end

%Parse the stuff that most folks do not care about.
otherInfo.line1Number = str2double(TLELine1(1));
otherInfo.line2Number = str2double(TLELine2(1));
otherInfo.satelliteNumber1 = str2double(TLELine1(3:7));
otherInfo.satelliteNumber2 = str2double(TLELine2(3:7));

otherInfo.classification = TLELine1(8);
otherInfo.IDLaunchYear = str2double(TLELine1(10:11));
otherInfo.IDLaunchNumber= str2double(TLELine1(12:14));
otherInfo.IDPieceOfLaunch= TLELine1(15:17);

%Convert revolutions per day squared into radians per second squared,
%with 86400 seconds per TT Julian day.
otherInfo.firstDerivMeanMotion=str2double(TLELine1(34:43))*(2*pi/86400^2);
%Convert revolutions per day cubed into radians per second cubed,
%with 86400 seconds per TT Julian day.
otherInfo.secondDerivMeanMotion=str2double(TLELine1(44:50))*10.0^str2double(TLELine1(51:52))*(2*pi/86400^3);
otherInfo.ephemerisType = str2double(TLELine1(63));
otherInfo.elementNumber = str2double(TLELine1(65:68));
otherInfo.revolutionNumberAtEpoch=str2double(TLELine2(64:68));

%Parse the stuff that most folks care about
inclination = str2double(TLELine2(8:16))*deg2Rad;
RAAscendingNode = str2double(TLELine2(17:25))*deg2Rad;
eccentricity = str2double(TLELine2(26:33));
argumentOfPerigee = str2double(TLELine2(34:42))*deg2Rad;
meanAnomaly = str2double(TLELine2(43:51))*deg2Rad;
%Convert from revolutions per day to radians per second, with 86400
%seconds per TT Julian day.
meanMotion=str2double(TLELine2(52:63))*(2*pi/86400);
BSTARDrag= str2double(TLELine1(53:59))*10.0^str2double(TLELine1(60:61));

%Get the time of the orbital elements.
epochYear = str2double(TLELine1(19:20));
epochDays = str2double(TLELine1(21:32));

%Assume that the years only run from 1957->2057 and find the actual
%epoch year.
if (epochYear < 57)
    year=epochYear+2000;
else
    year=epochYear+1900;
end
%Convert into a two-part Julian date in TT.
[month,dayOfMonth]=dayOfYear2MonthDay(year,fix(epochDays));
dayFrac=epochDays-fix(epochDays);
[hour,minute,second]=fracDayOfMonth2HourMinSec(year,month,dayOfMonth,dayFrac);
[TTEpoch1,TTEpoch2]=Cal2TT(year,month,dayOfMonth,hour,minute,second);

SGP4Elements=zeros(7,1);
SGP4Elements(1)=eccentricity;
SGP4Elements(2)=inclination;
SGP4Elements(3)=argumentOfPerigee;
SGP4Elements(4)=RAAscendingNode;
SGP4Elements(5)=meanAnomaly;
SGP4Elements(6)=meanMotion;
SGP4Elements(7)=BSTARDrag;
end

function isValid=validateTLEChecksum(theTLELine)
%The validation comes from the explanation of how the checksum is formed
%from http://www.celestrak.com/NORAD/elements/ It is the last digit of the
%sum of all of the digits in the string plus an extra 1 for each '-'.
    theSum=0;
    for curCharIdx=1:68
        curChar=theTLELine(curCharIdx);
        if(curChar=='-')
            theSum=theSum+1;
        elseif(curChar>='0'&&curChar<='9')
            theSum=theSum+str2double(curChar);
        end
    end

    %Get the last digit of the sum.
    sumStr=num2str(theSum);
    isValid=(sumStr(end)==theTLELine(69));
end

%LICENSE:
%
%The source code is in the public domain and not licensed or under
%copyright. The information and software may be used freely by the public.
%As required by 17 U.S.C. 403, third parties producing copyrighted works
%consisting predominantly of the material produced by U.S. government
%agencies must provide notice with such work(s) identifying the U.S.
%Government material incorporated and stating that such material is not
%subject to copyright protection.
%
%Derived works shall not identify themselves in a manner that implies an
%endorsement by or an affiliation with the Naval Research Laboratory.
%
%RECIPIENT BEARS ALL RISK RELATING TO QUALITY AND PERFORMANCE OF THE
%SOFTWARE AND ANY RELATED MATERIALS, AND AGREES TO INDEMNIFY THE NAVAL
%RESEARCH LABORATORY FOR ALL THIRD-PARTY CLAIMS RESULTING FROM THE ACTIONS
%OF RECIPIENT IN THE USE OF THE SOFTWARE.


function [mustUU, pos_mustUU, mustUU_linear, pos_mustUU_linear] = findMustUUWithGAMS(model, minFluxesW, maxFluxesW, constrOpt, excludedRxns, mustSetFirstOrder, solverName, runID, outputFolder, outputFileName, printExcel, printText, printReport, keepInputs, keepGamsOutputs, verbose)
% This function runs the second step of `optForce`, that is to solve a
% bilevel mixed integer linear programming  problem to find a second order
% `MustUU` set.
%
% USAGE:
%
%    [mustUU, pos_mustUU, mustUU_linear, pos_mustUU_linear] = findMustUUWithGAMS(model, minFluxesW, maxFluxesW, varargin)
%
% INPUTS:
%    model:                      (structure) a metabolic model with at
%                                least the following fields:
%
%                                  * .rxns - Reaction IDs in the model
%                                  * .mets - Metabolite IDs in the model
%                                  * .S -    Stoichiometric matrix (sparse)
%                                  * .b -    RHS of `Sv = b` (usually zeros)
%                                  * .c -    Objective coefficients
%                                  * .lb -   Lower bounds for fluxes
%                                  * .ub -   Upper bounds for fluxes
%    minFluxesW:                 (double array of size `n_rxns x 1`) minimum
%                                fluxes for each reaction in the model for
%                                wild-type strain. This can be obtained by
%                                running the function `FVAOptForce`. E.g.:
%                                `minFluxesW = [-90; -56];`
%    maxFluxesW:                 (double array of size `n_rxns x 1`) maximum
%                                fluxes for each reaction in the model for
%                                wild-type strain. This can be obtained by
%                                running the function `FVAOptForce`. E.g.:
%                                `maxFluxesW = [90; 56];`
%
% OPTIONAL INPUTS:
%    constrOpt:                  (Structure) structure containing
%                                additional contraints. Include here only
%                                reactions whose flux is fixed, i.e.,
%                                reactions whose lower and upper bounds
%                                have the same value. Do not include here
%                                reactions whose lower and upper bounds
%                                have different values. Such contraints
%                                should be defined in the lower and upper
%                                bounds of the model. The structure has the
%                                following fields:
%
%                                  * .rxnList - Reaction list (cell array)
%                                  * .values -  Values for constrained
%                                    reactions (double array)
%                                    E.g.: `struct('rxnList', {{'EX_gluc', 'R75', 'EX_suc'}}, 'values', [-100, 0, 155.5]');`
%    excludedRxns:               (cell array) Reactions to be excluded to
%                                the `MustUU` set. This could be used to
%                                avoid finding transporters or exchange
%                                reactions in the set. Default = empty.
%    mustSetFirstOrder:          (cell array) Reactions that belong to
%                                `MustU` and `MustL` (first order sets).
%                                Default = empty.
%    solverName:                 (string) Name of the solver used in
%                                GAMS. Default = 'cplex'.
%    runID:                      (string) ID for identifying this run.
%                                Default = ['run' date hour].
%    outputFolder:               (string) name for folder in which
%                                results will be stored. Default =
%                                'OutputsFindMustUU'.
%    outputFileName:             (string) name for files in which
%                                results. will be stored Default =
%                                'MustUUSet'.
%    printExcel:                 (double) boolean to describe wheter
%                                data must be printed in an excel file or
%                                not. Default = 1
%    printText:                  (double) boolean to describe wheter
%                                data must be printed in an plaint text
%                                file or not. Default = 1
%    printReport:                (double) 1 to generate a report in a
%                                plain text file. 0 otherwise. Default = 1
%    keepInputs:                 (double) 1 to mantain folder with
%                                inputs to run `findMustUU.gms`. 0 otherwise.
%                                Default = 1
%    keepGamsOutputs:            (double) 1 to mantain files returned by
%                                `findMustUU.gms`. 0 otherwise. Default = 1
%    verbose:                    (double) 1 to print results in console.
%                                0 otherwise. Default = 0
%
% OUTPUTS:
%    mustUU:                     (cell array of size number of sets found X
%                                2) Cell array containing the reactions IDs
%                                which belong to the `MustUU` set. Each row
%                                contain a couple of reactions that must
%                                decrease their flux.
%    pos_mustUU:                 (double array of size number of sets found
%                                X 2) double array containing the positions
%                                of each reaction in `mustUU` with regard to
%                                `model.rxns`
%    mustUU_linear:              (cell array of size number of unique
%                                reactions found X 1) Cell array containing
%                                the unique reactions ID which belong to
%                                the `MustUU` Set
%    pos_mustUU_linear:          (double array of size number of unique
%                                reactions found X 1) double array
%                                containing positions for reactions in
%                                `mustUU_linear`. with regard to `model.rxns`
%    outputFileName.xls:        (file) File containing one column
%                                array with identifiers for reactions in
%                                MustUU. This file will only be generated
%                                if the user entered `printExcel = 1`. Note
%                                that the user can choose the name of this
%                                file entering the input `outputFileName = 'PutYourOwnFileNameHere';`
%    outputFileName.txt:         (file) File containing one column
%                                array with identifiers for reactions in
%                                MustUU. This file will only be generated
%                                if the user entered `printText = 1`. Note
%                                that the user can choose the name of this
%                                file entering the input `outputFileName = 'PutYourOwnFileNameHere';`
%    outputFileName_Info.xls:    (file) File containing one column
%                                array. In each row the user will find a
%                                couple of reactions. Each couple of
%                                reaction was found in one iteration of
%                                `FindMustUU.gms`. This file will only be
%                                generated if the user entered `printExcel = 1`.
%                                Note that the user can choose the name
%                                of this file entering the input
%                                `outputFileName = 'PutYourOwnFileNameHere';`
%    outputFileName_Info.txt:    (file) File containing one column
%                                array. In each row the user will find a
%                                couple of reactions. Each couple of
%                                reaction was found in one iteration of
%                                `FindMustUU.gms`. This file will only be
%                                generated if the user entered `printText = 1`.
%                                Note that the user can choose the name
%                                of this file entering the input
%                                `outputFileName = 'PutYourOwnFileNameHere';`
%    findMustUU.lst:             (file) file autogenerated by GAMS. It
%                                contains information about equations,
%                                variables, parameters as well as
%                                information about the running (values at
%                                each iteration). This file only will be
%                                saved in the output folder is the user
%                                entered `keepGamsOutputs = 1`
%    GtoMUU.gdx:                 (file) file containing values for
%                                variables, parameters, etc. which were
%                                found by GAMS when solving `findMustUU.gms`.
%                                This file only will be saved in the output
%                                folder is the user entered `keepInputs = 1`
%
% NOTE:
%
%    This function is based in the GAMS files written by Sridhar
%    Ranganathan which were provided by the research group of Costas D.
%    Maranas. For a detailed description of the `optForce` procedure, please
%    see: `Ranganathan S, Suthers PF, Maranas CD (2010) OptForce: An
%    Optimization Procedure for Identifying All Genetic Manipulations
%    Leading to Targeted Overproductions. PLOS Computational Biology 6(4):
%    e1000744`. https://doi.org/10.1371/journal.pcbi.1000744
%
% .. Author: - Sebastian Mendoza, May 30th 2017, Center for Mathematical Modeling, University of Chile, snmendoz@uc.cl

optionalParameters = {'constrOpt', 'excludedRxns', 'mustSetFirstOrder', 'solverName', 'runID', 'outputFolder', 'outputFileName',  ...
    'printExcel', 'printText', 'printReport', 'keepInputs', 'keepGamsOutputs', 'verbose'};

if (numel(varargin) > 0 && (~ischar(varargin{1}) || ~any(ismember(varargin{1},optionalParameters))))

    tempargin = cell(1,2*(numel(varargin)));
    for i = 1:numel(varargin)

        tempargin{2*(i-1)+1} = optionalParameters{i};
        tempargin{2*(i-1)+2} = varargin{i};
    end
    varargin = tempargin;

end

parser = inputParser();
parser.addRequired('model', @(x) isstruct(x) && isfield(x, 'S') && isfield(model, 'rxns')...
    && isfield(model, 'mets') && isfield(model, 'lb') && isfield(model, 'ub') && isfield(model, 'b')...
    && isfield(model, 'c'))
parser.addRequired('minFluxesW', @isnumeric)
parser.addRequired('maxFluxesW', @isnumeric)
parser.addParamValue('constrOpt', struct('rxnList', {{}}, 'values', []),@ (x) isstruct(x) && isfield(x, 'rxnList') && isfield(x, 'values') ...
    && length(x.rxnList) == length(x.values) && length(intersect(x.rxnList, model.rxns)) == length(x.rxnList))
parser.addParamValue('excludedRxns', {}, @(x) iscell(x) && length(intersect(x, model.rxns)) == length(x))
parser.addParamValue('mustSetFirstOrder', {}, @(x) iscell(x) && length(intersect(x, model.rxns)) == length(x))
solvers = checkGAMSSolvers('MIP');
if isempty(solvers)
    error('there is no GAMS solvers available to solve Mixed Integer Programming problems') ;
else
    if ismember('cplex', lower(solvers))
        defaultSolverName = 'cplex';
    else
        defaultSolverName = lower(solvers(1));
    end
end

parser.addParamValue('solverName', defaultSolverName, @(x) ischar(x))
hour = clock; defaultRunID = ['run-' date '-' num2str(hour(4)) 'h' '-' num2str(hour(5)) 'm'];
parser.addParamValue('runID', defaultRunID, @(x) ischar(x))
parser.addParamValue('outputFolder', 'OutputsFindMustUU', @(x) ischar(x))
parser.addParamValue('outputFileName', 'MustUUSet', @(x) ischar(x))
parser.addParamValue('printExcel', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('printText', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('printReport', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('keepInputs', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('keepGamsOutputs', 1, @(x) isnumeric(x) || islogical(x));
parser.addParamValue('verbose', 1, @(x) isnumeric(x) || islogical(x));

parser.parse(model, minFluxesW, maxFluxesW, varargin{:})
model = parser.Results.model;
minFluxesW = parser.Results.minFluxesW;
maxFluxesW = parser.Results.maxFluxesW;
constrOpt= parser.Results.constrOpt;
excludedRxns= parser.Results.excludedRxns;
mustSetFirstOrder = parser.Results.mustSetFirstOrder;
solverName = parser.Results.solverName;
runID = parser.Results.runID;
outputFolder = parser.Results.outputFolder;
outputFileName = parser.Results.outputFileName;
printExcel = parser.Results.printExcel;
printText = parser.Results.printText;
printReport = parser.Results.printReport;
keepInputs = parser.Results.keepInputs;
keepGamsOutputs = parser.Results.keepGamsOutputs;
verbose = parser.Results.verbose;

% correct size of constrOpt
if ~isempty(constrOpt.rxnList)
    if size(constrOpt.rxnList, 1) > size(constrOpt.rxnList,2); constrOpt.rxnList = constrOpt.rxnList'; end;
    if size(constrOpt.values, 1) > size(constrOpt.values,2); constrOpt.values = constrOpt.values'; end;
end

% first, verify that GAMS is installed in your system
gamsPath = which('gams');
if isempty(gamsPath); error('OptForce: GAMS is not installed in your system. Please install GAMS.'); end;

%name of the function to solve the optimization problem in GAMS
gamsMustUUFunction = 'findMustUU.gms';
%path of that function
pathGamsFunction = which(gamsMustUUFunction);
if isempty(pathGamsFunction); error(['OptForce: ' gamsMustUUFunction ' not in MATLAB path.']); end;
%current path
workingPath = pwd;
%go to the path associate to the ID for this run.
if ~isdir(runID); mkdir(runID); end; cd(runID);

% if the user wants to generate a report.
if printReport
    %create name for file.
    hour = clock;
    reportFileName = ['report-' date '-' num2str(hour(4)) 'h' '-' num2str(hour(5)) 'm.txt'];
    freport = fopen(reportFileName, 'w');
    reportClosed = 0;
    % print date of running.
    fprintf(freport, ['findMustUUWithGAMS.m executed on ' date ' at ' num2str(hour(4)) ':' num2str(hour(5)) '\n\n']);
    % print matlab version.
    fprintf(freport, ['MATLAB: Release R' version('-release') '\n']);
    % print gams version.
    fprintf(freport, ['GAMS: ' regexprep(gamsPath, '\\', '\\\') '\n']);
    % print solver used in GAMS to solve optForce.
    fprintf(freport, ['GAMS solver: ' solverName '\n']);

    %print each of the inputs used in this running.
    fprintf(freport, '\nThe following inputs were used to run OptForce: \n');
    fprintf(freport, '\n------INPUTS------\n');
    %print model.
    fprintf(freport, '\nModel:\n');
    for i = 1:length(model.rxns)
        rxn = printRxnFormula(model, model.rxns{i}, false);
        fprintf(freport, [model.rxns{i} ': ' rxn{1} '\n']);
    end
    %print lower and upper bounds, minimum and maximum values for each of
    %the reactions in wild-type and mutant strain
    fprintf(freport, '\nLB\tUB\tMin_WT\tMax_WT\n');
    for i = 1:length(model.rxns)
        fprintf(freport, '%6.4f\t%6.4f\t%6.4f\t%6.4f\n', model.lb(i), model.ub(i), minFluxesW(i), maxFluxesW(i));
    end

    %print constraints
    fprintf(freport,'\nConstrained reactions:\n');
    for i = 1:length(constrOpt.rxnList)
        fprintf(freport,'%s: fixed in %6.4f\n',constrOpt.rxnList{i},constrOpt.values(i));
    end

    fprintf(freport, '\nExcluded Reactions:\n');
    for i = 1:length(excludedRxns)
        rxn = printRxnFormula(model, excludedRxns{i}, false);
        fprintf(freport, [excludedRxns{i} ': ' rxn{1} '\n']);
    end

    fprintf(freport, '\nReactions from first order sets(MustU and MustL):\n');
    for i = 1:length(mustSetFirstOrder)
        rxn = printRxnFormula(model, mustSetFirstOrder{i}, false);
        fprintf(freport, [mustSetFirstOrder{i} ': ' rxn{1} '\n']);
    end

    fprintf(freport,'\nrunID(Main Folder): %s \n\noutputFolder: %s \n\noutputFileName: %s \n',...
        runID, outputFolder, outputFileName);

    fprintf(freport,'\nprintExcel: %1.0f \n\nprintText: %1.0f \n\nprintReport: %1.0f \n\nkeepInputs: %1.0f  \n\nkeepGamsOutputs: %1.0f \n\nverbose: %1.0f \n',...
        printExcel,printText,printReport,keepInputs,keepGamsOutputs,verbose);

end

copyfile(pathGamsFunction);

% export inputs for running the optimization problem in GAMS to find the
% MustUU Set
inputFolder = 'InputsMustUU';
exportInputsMustOrder2ToGAMS(model, 'UU', minFluxesW, maxFluxesW, constrOpt, excludedRxns, mustSetFirstOrder, inputFolder)

% create a directory to save results if this don't exist
if ~exist(outputFolder, 'dir')
    mkdir(outputFolder);
end

%run
if verbose
    run = system(['gams ' gamsMustUUFunction ' lo=3 --myroot=' inputFolder '/ --solverName=' solverName ' gdx=GtoMUU']);
else
    run=system(['gams ' gamsMustUUFunction ' --myroot=' inputFolder '/ --solverName=' solverName ' gdx=GtoMUU']);
end

if printReport; fprintf(freport, '\n------RESULTS------\n'); end;

%if user decide not to show inputs files for findMustUU.gms
if ~keepInputs; rmdir(inputFolder,'s'); end;

%if findMustUU.gms was executed correctly "run" should be 0
if run == 0

    if printReport; fprintf(freport,'\nGAMS was executed correctly\n'); end;
    if verbose; fprintf('GAMS was executed correctly\nSummary of information exported by GAMS:\n'); end;
    %show GAMS report in MATLAB console
    if verbose; gdxWhos GtoMUU; end;
    try
        findMustUU.name = 'findMustUU';
        rgdx('GtoMUU',findMustUU); %if do not exist the variable findMustUU in GtoMUU, an error will ocurr.
        if printReport; fprintf(freport,'\nGAMS variables were read by MATLAB correctly\n'); end;
        if verbose; fprintf('GAMS variables were read by MATLAB correctly\n'); end;

        %Using GDXMRW to read solutions found by findMustUU.gms
        %extract matrix 1 found by findMustII.gms. This matrix contains the
        %first reaction in each couple of reactions
        m1.name = 'matrix1';
        m1.compress = 'true';
        m1 = rgdx('GtoMUU',m1);
        uels_m1 = m1.uels{2};

        if ~isempty(uels_m1)
            %if the uel array for m1 is not empty, at least 1 couple of reations was found.
            if printReport; fprintf(freport,'\na MustUU set was found\n'); end;
            if verbose; fprintf('a MustUU set was found\n'); end;

            %find values for matrix 1
            val_m1 = m1.val;
            m1_full = full(sparse(val_m1(:,1),val_m1(:,2:end-1),val_m1(:,3)));

            %find values for matrix 2
            m2.name = 'matrix2';
            m2.compress = 'true';
            m2 = rgdx('GtoMUU',m2);
            uels_m2 = m2.uels{2};
            val_m2 = m2.val;
            m2_full = full(sparse(val_m2(:,1),val_m2(:,2:end-1),val_m2(:,3)));

            %initialize empty array for storing
            n_mustSet = size(m1_full,1);
            mustUU = cell(n_mustSet,2);
            pos_mustUU = zeros(size(mustUU));
            mustUU_linear = {};

            %write each couple of reactions.
            for i = 1:n_mustSet
                rxn1 = uels_m1(m1_full(i,:) == 1);
                rxn2 = uels_m2(m2_full(i,:) == 1);
                mustUU(i,1) = rxn1;
                mustUU(i,2) = rxn2;
                pos_mustUU(i,1) = find(strcmp(model.rxns,rxn1));
                pos_mustUU(i,2) = find(strcmp(model.rxns,rxn2));
                mustUU_linear = union(mustUU_linear,[rxn1;rxn2]);
            end
            pos_mustUU_linear = cell2mat(arrayfun(@(x)find(strcmp(x,model.rxns)),mustUU_linear,'UniformOutput', false))';
        else
            %if the uel array for m1 is empty, no couple of reations was found.
            if printReport; fprintf(freport,'\na MustUU set was not found\n'); end;
            if verbose; fprintf('a MustUU set was not found\n'); end;

            %initialize arrays to be returned by this function
            mustUU = {};
            pos_mustUU = [];
            mustUU_linear = {};
            pos_mustUU_linear = [];
        end

        % print info into an excel file if required by the user
        if printExcel
            if  ~isempty(uels_m1)
                currentFolder = pwd;
                cd(outputFolder);
                must = cell(size(mustUU,1),1);
                for i = 1:size(mustUU,1)
                    must{i} = strjoin(mustUU(i,:),' or ');
                end
                xlswrite([outputFileName '_Info'],[{'Reactions'};must]);
                xlswrite(outputFileName,mustUU_linear);
                cd(currentFolder);
                if verbose
                    fprintf(['MustUU set was printed in ' outputFileName '.xls  \n']);
                    fprintf(['MustUU set was also printed in ' outputFileName '_Info.xls  \n']);
                end
                if printReport
                    fprintf(freport,['\nMustUU set was printed in ' outputFileName '.xls  \n']);
                    fprintf(freport,['\nMustUU set was printed in ' outputFileName '_Info.xls  \n']);
                end
            else
                if verbose; fprintf('No mustUU set was found. Therefore, no excel file was generated\n'); end;
                if printReport; fprintf(freport, '\nNo mustUU set was found. Therefore, no excel file was generated\n'); end;
            end
        end

        % print info into a plain text file if required by the user
        if printText
            if ~isempty(uels_m1)
                currentFolder = pwd;
                cd(outputFolder);
                f = fopen([outputFileName '_Info.txt'],'w');
                fprintf(f,'Reactions\n');
                for i = 1:size(mustUU,1)
                    fprintf(f,'%s or %s\n',mustUU{i,1},mustUU{i,2});
                end
                fclose(f);

                f = fopen([outputFileName '.txt'],'w');
                for i = 1:length(mustUU_linear)
                    fprintf(f,'%s\n',mustUU_linear{i});
                end
                fclose(f);

                cd(currentFolder);
                if verbose
                    fprintf(['MustUU set was printed in ' outputFileName '.txt  \n']);
                    fprintf(['MustUU set was also printed in ' outputFileName '_Info.txt  \n']);
                end
                if printReport
                    fprintf(freport,['\nMustUU set was printed in ' outputFileName '.txt  \n']);
                    fprintf(freport,['\nMustUU set was printed in ' outputFileName '_Info.txt  \n']);
                end

            else
                if verbose; fprintf('No mustUU set was found. Therefore, no plain text file was generated\n'); end;
                if printReport; fprintf(freport, '\nNo mustUU set was found. Therefore, no plain text file was generated\n'); end;
            end
        end

        %close file for saving report
        if printReport; fclose(freport); reportClosed = 1; end;
        if printReport; movefile(reportFileName,outputFolder); end;
        delete(gamsMustUUFunction);

        %remove or move additional files that were generated during running
        if keepGamsOutputs
            if ~isdir(outputFolder); mkdir(outputFolder); end;
            movefile('GtoMUU.gdx',outputFolder);
            movefile(regexprep(gamsMustUUFunction,'gms','lst'),outputFolder);
        else
            delete('GtoMUU.gdx');
            delete(regexprep(gamsMustUUFunction,'gms','lst'));
        end

        %go back to the original path
        cd(workingPath);
    catch
        %GAMS variables were not read correctly by MATLAB
        if verbose; fprintf('GAMS variables were not read by MATLAB corretly\n'); end;
        if printReport && ~reportClosed; fprintf(freport, '\nGAMS variables were not read by MATLAB corretly\n'); fclose(freport); end;
        cd(workingPath);
        error('OptForce: GAMS variables were not read by MATLAB corretly');

    end

    %if findMustUU.gms was not executed correctly "run" should be different from 0
else
    %if GAMS was not executed correcttly
    if printReport && ~reportClosed; fprintf(freport, '\nGAMS was not executed correctly\n'); fclose(freport); end;
    if verbose; fprintf('GAMS was not executed correctly\n'); end;
    cd(workingPath);
    error('OptForce: GAMS was not executed correctly');

end

end


function [varargout]=cmaperise(varargin)

% function [Cmapped,indMap]=cmaperise(C,cmap,clim)
%-------------------------------------------------------------------------
% This function creates RGB colors for the data C using the colormap cmap
% and the limits clim.
%
%
% Change log:
% 2019/06/25 Added variable input handling
%-------------------------------------------------------------------------

%% Parse input

switch nargin
    case 1
        C=varargin{1};
        cmap=[];
        clim=[];
    case 2
        C=varargin{1};
        cmap=varargin{2};
        clim=[];
    case 3
        C=varargin{1};
        cmap=varargin{2};
        clim=varargin{3};
end

if isempty(cmap)
    cmap=viridis(250);
end

if isempty(clim)
   clim=[min(C(:)) max(C(:))];
   if diff(clim)<eps(0)
       clim=[min(C(:))-1e-3 max(C(:))+1e-3];
   end
end

%% Use colormap to define RGB values

C=C(:); %Color data as column

p=(C-clim(1))./(clim(2)-clim(1));
p(p<0)=0;
p(p>1)=1;

p(isnan(p))=0; %Force nan data to 0

IND_cmap=round((p*(size(cmap,1)-1))+1);
Cmapped=cmap(IND_cmap,:);

%%
varargout{1}=Cmapped;
if nargout==2
    varargout{2}=IND_cmap;
end

end

%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% This program 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.
%
% This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.


% easy11    Creates a stereographic plot of GPS satellite  orbits
%                from an epehemerides or almanac file. The plot is as
%                seen from the position (phi, lambda). All (parts of)
%                orbits with elevation angle lower than a given
%                cut-off angle (mask) are omitted.
%                As input we use the ephemerides in our basic RINEX
%               navigation file. It only contains nine ephemerides. The
%               result shows two windows with a considerable amount
%               of visible satellites. This is like the actual situation in the
%               early 1990es. Then you could have a window  of about
%               one hour at say 5--6 am and this repeated 12 hours later.
%
%              An additional plot is created showing number
%              of visible satellites and when they are visible.
%              Finally a stem plot depicts the number of
%             satelliets visible for how long
%
%             Alternatively, if you want to include all GPS satellites,
%             we need an almanac file which can be downloaded from
%             http://www.navcen.uscg.gov/?pageName=gpsAlmanacs

%Kai Borre 26-08-2008
%Copyright (c) by Kai Borre
%$Revision: 1.1 $  $Date: 2009/04/05  $
% Total revision January 10, 2016

% RINEX version 3.03

set(0,'DefaultTextFontName','Times');
set(0,'DefaultAxesFontName','Times');
set(0,'DefaultTextFontSize',12);

% If you want to use a RINEX ephemeris file uncomment
% line 37 and comment line 41 out.

% the following five assignments are mandatory
%%rinexe('log_24h.15n','eph.dat');
% in  case you want to include all GPS satellites in the constellation,
% we need an almanac. This is read and converted to the usual
% eph format by the following call
rinexa('current.alm','eph.dat');

ephemerides = 'eph.dat';
mask = 10;
phi = [57 12 43];
lambda = [50 10 39];

%reading ephemerides
fide = fopen(ephemerides,'r');
Eph = fread(fide,inf,'double');
m = length(Eph);
eph = reshape(Eph,21,m/21);

% transformation of given location (phi,lambda,h) to (X,Y,Z)
Phi = dms2rad(phi(1),phi(2),phi(3));
Phi = Phi*180/pi;
Lambda = dms2rad(lambda(1),lambda(2),lambda(3));
Lambda = Lambda*180/pi;
[M(1,1),M(2,1),M(3,1)] = frgeod(6378137,298.257222101,Phi,Lambda,0);

% Computation of (azimuth, elevation) for each satellite
% for each 15 min.s. We use only one ephemeris for each PRN.
% Anyway, the plot is only for visual use
[prns, ind] = unique(eph(1,:));
satrows = length(ind);
 az = ones(satrows,96)*inf; % satrows is number of PRN and 96 quaters of an hour in 24 hours
el = ones(satrows,96)*inf;

start_time = max(eph(21,:));
for sat = ind'
    j = 0;
    for time = start_time:900:start_time+86400
        S = satpos(time,eph(:,sat)); %sat
        j = j+1;
        [azimuth,elevation,distance] = topocent(M,S-M);
        az(sat,j) = azimuth; % sat runs over PRN, j runs over sow
        el(sat,j) = elevation;
    end
end
% We assume that there are satrows PRNs
XX = zeros(satrows,40)*inf; % a matrix of NaNs to store plot data
YY = XX;

figure(1);
% polarhg draws coordinate lines of a polar plot. We add
% circles with radii 30 and 60 degrees
polarhg([30 60])
hold on
for k = ind' % 1:32
    % if az(k,1) == 0, break, end  % continue
    AZ = az(k,:);
    EL = el(k,:);
    % remove data below the cut-off angle
    AZ(find(EL <= mask)) = nan;
    EL(find(EL <= mask)) = nan;
    % conversion from polar to rectangular coordinates
    xx = (90-EL).*cos(AZ*pi/180);
    yy = (90-EL).*sin(AZ*pi/180);
    XX(k,1:length(xx)) = xx;
    YY(k,1:length(yy)) = yy;
end % k
%the first coord. moves text vertically (increasing values up),
% the second coord. moves text horizontally (increasing values right)
text(135,-95,{['Skyplot for the position (\phi, \lambda) = (' ...
    num2str(round(Phi)) '\circ, '  num2str(round(Lambda)) '\circ)']})
text(115,-45,{['Elevation mask  ' num2str(mask) '\circ' ]}) %120
text(-120,-120,['All PRNs except  ' num2str(setdiff(1:32,prns)) ])
plot(XX',YY','linewidth',2)
hold off
set(gca,'Fontsize',9);

print('easy111','-dpdf')

% preparation for visibility plot  %%%

% we choose a resolution of 5 min.s,
% ie. 24 hours times 12 = 288 which becomes the range of j
satsum = zeros(1,288);
visible = zeros(2*(size(prns,2)+1),288);

for sat = ind'
    %Az = [];
    %El = [];
    i = eph(1,sat);
    for j = 1:288
        time = 300*(j-1); % in s, 0<time<86400
        S = satpos(time,eph(:,sat));
        [az,el,d] = topocent(M,S-M);
        if el > mask
 %           Az = [Az az];
  %          El = [El el];
            satsum(j) = satsum(j)+1;
            visible(2*i,j) = 1;
        end
    end
end

figure(2);
set(gca,'Fontsize',16);
area(satsum)
set(gca,'XTick',1:71:288)
set(gca,'XTickLabel',{'0','6','12','18','24'})
xlabel('GPS Time [hours]')
ylabel('# of Visible Satellites')
title(['Elevation Mask ' num2str(mask) '\circ'])
colormap summer

print -dpdf easy112

figure(3);
set(gca,'Fontsize',16);
imagesc(flipud(visible));
colormap(autumn(5))
set(gca,'XTick',1:71:288)
set(gca,'XTickLabel',{'0','6','12','18','24'})
set(gca,'YTick',-3:16:(2*(size(prns,2)+1)))
set(gca,'YTickLabel',{'2','8','16','24','32'});
xlabel('GPS Time [hours]')
ylabel('PRNs')
title('Yellow Lines Indicate Visible Satellites')
colormap winter

print -dpdf easy113

figure(4);
set(gca,'Fontsize',16);
an = sum(visible,1);
% we assume the total # of PRNs always is between 1 and 20
total = zeros(1,20);
for i = 1:288
    j = an(i);
    if j == 0, j = j+1; total(j) = 0.01; continue, end
    total(j) = total(j)+1;
end
% the adder total adds per 5 minutes, division by 12 changes to per hour
bar(1:20,total/12);  % 10-> 32
title('Number of Visible Satellites')
ylabel('Hours')

print -dpdf easy114

%%%%%%%%%%%%%%%%%%%%% end easy11.m %%%%%%%%%%%%%%


% MSH_EVALUATE_ELEMENT_LIST: evaluate the parameterization in a given list of elements.
%
%     msh_elems = msh_evaluate_element_list (msh, elements)
%
% INPUTS:
%
%    msh:          mesh object (see msh_cartesian)
%    element_list: numbering of the elements where the evaluations are performed.
%
% OUTPUT:
%
%     msh_elems: structure containing the quadrature rule in the given elements of the physical domain, which contains the following fields
%
%     FIELD_NAME         (SIZE)                  DESCRIPTION
%     npatch             (scalar)                number of patches
%     ndim               (scalar)                dimension of the parametric space
%     rdim               (scalar)                dimension of the physical space
%     nel                (scalar)                number of elements in the list
%     elem_list          (1 x nel)               numbering of the elements in the list
%     nqn                (scalar)                number of quadrature points per element (must be the same for every patch)
%     nqn_dir            (1 x ndim)              number of quadrature points in each direction (must be the same for every patch)
%     nel_per_patch      (1 x npatch)            number of selected elements on each patch
%     elem_list_of_patch (1 x npatch cell-array) selected elements on the patch, with local numbering
%     nel_dir_of_patch   (1 x npatch cell-array) the total number of elements in each direction, for each patch
%     quad_weights, geo_map, geo_map_jac, deo_map_der2, jacdet, element_size (see msh_evaluate_col for details)
%
% The function only works if the number of quadrature points is the same for all the patches and all directions.
%
% Copyright (C) 2015 Rafael Vazquez
%
%    This program 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.

%    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

function msh_col = msh_evaluate_element_list (msh, elem_list, varargin)

  elem_list = elem_list(:)';

  msh_col.npatch = msh.npatch;
  msh_col.ndim = msh.ndim;
  msh_col.rdim = msh.rdim;

  msh_col.nel  = numel (elem_list);
  msh_col.elem_list = elem_list;

  if (isempty (elem_list)), return, end

  fields = {'quad_weights', 'geo_map', 'geo_map_jac', 'geo_map_der2', 'jacdet', 'element_size'};
  cat_position = [2 3 4 5 2 2];
  for ii = 1:numel (fields)
    msh_col.(fields{ii}) = [];
  end

  Nelem = cumsum ([0, msh.nel_per_patch]);

  indices = cell (1, msh.npatch);
  for iptc = 1:msh.npatch
    [~,indices{iptc},~] = intersect ((Nelem(iptc)+1):Nelem(iptc+1), elem_list);
  end
  active_patches = find (~cellfun (@isempty, indices));

  for iptc = active_patches
    msh_patch = msh_evaluate_element_list (msh.msh_patch{iptc}, indices{iptc});

    if (msh_patch.nqn_dir ~= msh.msh_patch{active_patches(1)}.nqn_dir)
      error ('msh_evaluate_element_list: for multipatch geometries, the number of quadrature points should be the same on each patch')
    end

    for ii = 1:numel (fields)
      if (isfield (msh_patch, fields{ii}))
        msh_col.(fields{ii}) = cat (cat_position(ii), msh_col.(fields{ii}), msh_patch.(fields{ii}));
      end
    end
  end

  msh_col.nel_per_patch = cellfun (@numel, indices);
  msh_col.elem_list_of_patch = indices;
  msh_col.nel_dir_of_patch = cell (1, msh.npatch);
  for iptc = 1:msh.npatch
    msh_col.nel_dir_of_patch{iptc} = msh.msh_patch{iptc}.nel_dir;
  end
  msh_col.nqn = msh.msh_patch{active_patches(1)}.nqn;
  msh_col.nqn_dir = msh.msh_patch{active_patches(1)}.nqn_dir;

end


function varargout = sound_field_mono_wfs(X,Y,Z,xs,src,f,conf)
%SOUND_FIELD_MONO_WFS sound field for WFS
%
%   Usage: [P,x,y,z,x0] = sound_field_mono_wfs(X,Y,Z,xs,src,f,conf)
%
%   Input parameters:
%       X           - x-axis / m; single value or [xmin,xmax] or nD-array
%       Y           - y-axis / m; single value or [ymin,ymax] or nD-array
%       Z           - z-axis / m; single value or [zmin,zmax] or nD-array
%       xs          - position of virtual source / m
%       src         - source type of the virtual source
%                         'pw' - plane wave (xs is the direction of the
%                                plane wave in this case)
%                         'ps' - point source
%                         'fs' - focused source
%       f           - monochromatic frequency / Hz
%       conf        - configuration struct (see SFS_config)
%
%   Output parameters:
%       P           - simulated sound field
%       x           - corresponding x values / m
%       y           - corresponding y values / m
%       z           - corresponding z values / m
%       x0          - active secondary sources / m
%
%   SOUND_FIELD_MONO_WFS(X,Y,Z,xs,src,f,conf) simulates a monochromatic sound
%   field for the given source type (src) synthesized with wave field synthesis
%   for the frequency f.
%
%   To plot the result use:
%   plot_sound_field(P,X,Y,Z,x0,conf);
%   or simple call the function without output argument:
%   sound_field_mono_wfs(X,Y,Z,xs,src,f,conf)
%
%   See also: plot_sound_field, sound_field_imp_wfs, driving_function_mono_wfs

%*****************************************************************************
% The MIT License (MIT)                                                      *
%                                                                            *
% Copyright (c) 2010-2019 SFS Toolbox Developers                             *
%                                                                            *
% Permission is hereby granted,  free of charge,  to any person  obtaining a *
% copy of this software and associated documentation files (the "Software"), *
% to deal in the Software without  restriction, including without limitation *
% the rights  to use, copy, modify, merge,  publish, distribute, sublicense, *
% and/or  sell copies of  the Software,  and to permit  persons to whom  the *
% Software is furnished to do so, subject to the following conditions:       *
%                                                                            *
% The above copyright notice and this permission notice shall be included in *
% all copies or substantial portions of the Software.                        *
%                                                                            *
% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
% IMPLIED, INCLUDING BUT  NOT LIMITED TO THE  WARRANTIES OF MERCHANTABILITY, *
% FITNESS  FOR A PARTICULAR  PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL *
% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
% LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT, TORT  OR OTHERWISE, ARISING *
% FROM,  OUT OF  OR IN  CONNECTION  WITH THE  SOFTWARE OR  THE USE  OR OTHER *
% DEALINGS IN THE SOFTWARE.                                                  *
%                                                                            *
% The SFS Toolbox  allows to simulate and  investigate sound field synthesis *
% methods like wave field synthesis or higher order ambisonics.              *
%                                                                            *
% https://sfs.readthedocs.io                            sfstoolbox@gmail.com *
%*****************************************************************************

%% ===== Checking of input  parameters ==================================
nargmin = 7;
nargmax = 7;
narginchk(nargmin,nargmax);
isargxs(xs);
isargpositivescalar(f);
isargchar(src);
isargstruct(conf);

%% ===== Configuration ==================================================
if strcmp('2D',conf.dimension)
    greens_function = 'ls';
else
    greens_function = 'ps';
end

%% ===== Computation ====================================================
% Get the position of the loudspeakers and its activity
x0 = secondary_source_positions(conf);
x0 = secondary_source_selection(x0,xs,src);
x0 = secondary_source_tapering(x0,conf);
% Driving function
D = driving_function_mono_wfs(x0,xs,src,f,conf);
% Wave field
[varargout{1:min(nargout,4)}] = ...
    sound_field_mono(X,Y,Z,x0,greens_function,D,f,conf);
% Return secondary sources if desired
if nargout==5, varargout{5}=x0; end


function [y,z]=amen_sum(X, c, tol, varargin)
%Approximate the linear combination of TT tensors via the AMEn iteration
%   [y,z]=amen_sum(X, v, tol, varargin)
%   Attempts to approximate the y(j) = sum_i X{i}*c(i,j)
%   with accuracy TOL using the AMEn+ALS iteration.
%
%   Options are provided in form
%   'PropertyName1',PropertyValue1,'PropertyName2',PropertyValue2 and so
%   on. The parameters are set to default (in brackets in the following)
%   The list of option names and default values are:
%       o y0 - initial approximation to y [rand rank-2]
%       o nswp - maximal number of sweeps [20]
%       o verb - verbosity level, 0-silent, 1-sweep info, 2-block info [1]
%       o kickrank - compression rank of the error,
%         i.e. enrichment size [3]
%       o init_qr - perform QR of the input (save some time in ts, etc) [true]
%       o renorm - Orthog. and truncation methods: direct (svd,qr) or gram
%         (apply svd to the gram matrix, faster for m>>n) [direct]
%       o fkick - Perform solution enrichment during forward sweeps [false]
%         (rather questionable yet; false makes error higher, but "better
%         structured": it does not explode in e.g. subsequent matvecs)
%       o z0 - initial approximation to the error Ax-y [rand rank-kickrank]
%       o can - whether the input is in CP format [false]
%       o multrank - shall we try to grow ranks multiplicatively by adding
%       the previous iterand [false]
%
%
%********
%   For description of adaptive ALS please see
%   Sergey V. Dolgov, Dmitry V. Savostyanov,
%   Alternating minimal energy methods for linear systems in higher dimensions.
%   Part I: SPD systems, http://arxiv.org/abs/1301.6068,
%   Part II: Faster algorithm and application to nonsymmetric systems, http://arxiv.org/abs/1304.1222
%
%   Use {sergey.v.dolgov, dmitry.savostyanov}@gmail.com for feedback
%********
%
% TT-Toolbox 2.2, 2009-2012
%
%This is TT Toolbox, written by Ivan Oseledets et al.
%Institute of Numerical Mathematics, Moscow, Russia
%webpage: http://spring.inm.ras.ru/osel
%
%For all questions, bugs and suggestions please mail
%ivan.oseledets@gmail.com
%---------------------------

% Special case: amen_sum called from another amen package
if (~isempty(varargin))
    v1 = varargin{1};
    if (isa(v1, 'cell'))
        varargin=v1;
    end;
end;

% Default valuse
nswp = 20;
kickrank = 4;
kickrank2 = 0;
verb = 1;
y = [];
z = [];
init_qr = true;
renorm = 'direct';
% renorm = 'gram';
fkick = false;
rmax = Inf;
multrank = false;

can = false; % Whether the input is the canonical format

% Read parameters from input sequence
for i=1:2:length(varargin)-1
    switch lower(varargin{i})
        case 'nswp'
            nswp=varargin{i+1};
        case 'y0'
            y=varargin{i+1};
        case 'z0'
            z=varargin{i+1};
        case 'verb'
            verb=varargin{i+1};
        case 'kickrank'
            kickrank=varargin{i+1};
        case 'kickrank2'
            kickrank2=varargin{i+1};
        case 'init_qr'
            init_qr = varargin{i+1};
        case 'renorm'
            renorm = varargin{i+1};
        case 'fkick'
            fkick = varargin{i+1};
        case 'rmax'
            rmax = varargin{i+1};
        case 'can'
            can = varargin{i+1};
        case 'multrank'
            multrank = varargin{i+1};

        otherwise
            warning('Unrecognized option: %s\n',varargin{i});
    end
end

% Convert input to canonical or TT cell array
N = size(c,1);
M = size(c,2);
Xin = X;

if (can)
    N = 1;
    % X itself is already given in the required format
    d = numel(X);
    R = size(X{1}, 2);
    nrms = ones(d,1);
    n = zeros(d,1);
    for i=1:d
        n(i) = size(X{i}, 1);
    end;
    vectype = 1;
else
    R = 1;
    for j=1:N
        if (isa(Xin{j}, 'tt_tensor'))
            if (j==1)
                d = Xin{j}.d;
                n = Xin{j}.n;
                X = cell(d,N);

                % A storage for norms.
                % Z may be kept normalized, but for y we will have y_real = y*prod(nrms).
                % The same for all x. But note that we keep only the _scale_ for all x vecs.
                nrms = ones(d,1);
            end;
            X(:,j) = core2cell(Xin{j});
            vectype = 1; % tt_tensor
        else
            if (j==1)
                d = numel(Xin{j});
                n = zeros(d,1);
                for i=1:d
                    n(i) = size(Xin{j}{i}, 2);
                end;
                X = cell(d,N);
            end;
            X(:,j) = Xin{j};
            vectype = 0; % cell
        end;
    end;
end;

% Initial guess
if (isempty(y))
    init_qr = false;
    [y,ry] = gen_rand(n,d,2);
else
    if (isa(y, 'tt_tensor'))
        y = core2cell(y);
    end;
    ry = ones(d+1,1);
    for i=1:d
        ry(i+1) = size(y{i}, 3);
    end;
end;

% Enrichment vector
if (kickrank+kickrank2>0)
    if (isempty(z))
        [z,rz] = gen_rand(n,d,kickrank+kickrank2);
        init_qr_z = false;
    else
        init_qr_z = true;
        if (isa(z, 'tt_tensor'))
            z = core2cell(z);
        end;
        rz = ones(d+1,1);
        for i=1:d
            rz(i+1) = size(z{i}, 3);
        end;
    end;

    phizx = cell(d+1,N);
    for j=1:N
        phizx{1,j}=ones(1,R); phizx{d+1,j}=ones(R,1);
    end;
    phizy = cell(d+1,1); phizy{1}=1; phizy{d+1}=1;
end;

phiyx = cell(d+1,N);
for j=1:N
    phiyx{1,j}=ones(1,R); phiyx{d+1,j}=ones(R,1);
end;

% Initial ort
for i=1:d-1
    if (init_qr)
        cr = reshape(y{i}, ry(i)*n(i), ry(i+1));
        if (strcmp(renorm, 'gram'))&&(ry(i)*n(i)>5*ry(i+1))
            [cr,~,R]=svdgram(cr);
        else
            [cr,R]=qr(cr, 0);
        end;
        nrmr = norm(R, 'fro');
        if (nrmr>0)
            R = R/nrmr;
        end;
        cr2 = reshape(y{i+1}, ry(i+1), n(i+1)*ry(i+2));
        cr2 = R*cr2;
        ry(i+1) = size(cr, 2);
        y{i} = reshape(cr, ry(i), n(i), ry(i+1));
        y{i+1} = reshape(cr2, ry(i+1), n(i+1), ry(i+2));
    end;
    % We will extract the norms of Y'X
    [phiyx(i+1,:),nrms(i)] = compute_next_Phi(phiyx(i,:), y{i}, X(i,:), 'lr', can);

    if (kickrank+kickrank2>0)
        if (init_qr_z)
            cr = reshape(z{i}, rz(i)*n(i), rz(i+1));
            if (strcmp(renorm, 'gram'))&&(rz(i)*n(i)>5*rz(i+1))
                [cr,~,R]=svdgram(cr);
            else
                [cr,R]=qr(cr, 0);
            end;
            nrmr = norm(R, 'fro');
            if (nrmr>0)
                R = R/nrmr;
            end;
            cr2 = reshape(z{i+1}, rz(i+1), n(i+1)*rz(i+2));
            cr2 = R*cr2;
            rz(i+1) = size(cr, 2);
            z{i} = reshape(cr, rz(i), n(i), rz(i+1));
            z{i+1} = reshape(cr2, rz(i+1), n(i+1), rz(i+2));
        end;

        % But we have to renorm Z'X/|Y'X| to keep Z in the same
        % scale. Hope it will not make |Z'X| too small
        phizx(i+1,:) = compute_next_Phi(phizx(i,:), z{i}, X(i,:), 'lr', can, nrms(i));
        % Z'Y is actually an orthogonal matrix and does not require renorm
        phizy(i+1) = compute_next_Phi(phizy(i), z{i}, y(i), 'lr');
    end;
end;

i = d;
dir = -1;
swp = 1;
max_dx = 0;
y{d} = reshape(y{d}, ry(d)*n(d), ry(d+1));
y{d} = y{d}(:,1:min(ry(d+1), M));
y{d} = [y{d}, zeros(ry(d)*n(d), M-ry(d+1))];
y{d} = reshape(y{d}, ry(d), n(d), 1, M);
ry(d+1) = 1;

while (swp<=nswp)
    % Project the sum
    cry = proj_sum(phiyx(i,:), X(i,:), phiyx(i+1,:), c);
    % It will survive in the terminal block. All we need.
    nrms(i) = norm(cry, 'fro');
    % The main goal is to keep y{i} of norm 1
    if (nrms(i)>0)
        cry = cry/nrms(i);
    else
        nrms(i)=1;
    end;

    % Check stopping criteria
    y{i} = reshape(y{i}, ry(i)*n(i)*ry(i+1), M);
    dx = norm(cry-y{i}, 'fro')/norm(cry, 'fro');
    max_dx = max(max_dx, dx);

    % Truncation and enrichment
    if ((dir>0)&&(i<d))
        cry = reshape(cry, ry(i)*n(i), ry(i+1)*M);
        if (M==1)&&(multrank)
            % Try to accelerate the rank growth
            cry = [cry, reshape(y{i}, ry(i)*n(i), ry(i+1))];
        end;
        if (strcmp(renorm, 'gram'))
            [u,~,v]=svdgram(cry, tol/sqrt(d));
            v = v.';
            if (size(u,2)>rmax)
                u = u(:,1:rmax);
                v = v(:,1:rmax);
            end;
            r = size(u,2);
        else
            [u,s,v]=svd(cry, 'econ');
            s = diag(s);
            r = my_chop2(s, tol*norm(s)/sqrt(d));
            r = min(r,rmax);
            u = u(:,1:r);
            v = conj(v(:,1:r))*diag(s(1:r));
        end;
        if (M==1)&&(multrank)
            % Remove auxiliary previous solution
            v = reshape(v, ry(i+1), 2, r);
            v = v(:,1,:);
            v = reshape(v, ry(i+1), r);
        end;

        % Prepare enrichment, if needed
        if (kickrank+kickrank2>0)
            cry = u*v.';
            cry = reshape(cry, ry(i)*n(i)*ry(i+1), M);
            % For updating z
            crys = cry.';
            crys = reshape(crys, M*ry(i)*n(i), ry(i+1));
            crys = crys*phizy{i+1};
            crys = reshape(crys, M, ry(i)*n(i)*rz(i+1));
            crys = crys.';
            cryz = reshape(crys, ry(i), n(i)*rz(i+1)*M);
            cryz = phizy{i}*cryz;
            cryz = reshape(cryz, rz(i)*n(i)*rz(i+1), M);

            crz = proj_sum(phizx(i,:), X(i,:), phizx(i+1,:), c);
            crz = crz/nrms(i) - cryz;
            nrmz = norm(crz, 'fro');
            crz = reshape(crz, rz(i)*n(i), rz(i+1)*M);
            [crz,~,~]=svd(crz, 'econ');
            crz = crz(:, 1:min(size(crz,2), kickrank));
            if (kickrank2>0)
                crz = [crz, randn(rz(i)*n(i), kickrank2)];
                [crz,~]=qr(crz,0);
            end;
            % For adding into solution
            if (fkick)
                crs = proj_sum(phiyx(i,:), X(i,:), phizx(i+1,:), c);
                crs = crs/nrms(i) - crys;
                crs = reshape(crs, ry(i)*n(i), rz(i+1)*M);
                [crs,~,~]=svd(crs, 'econ');
                crs = crs(:, 1:min(size(crs,2), kickrank));
                u = [u,crs];
                if (strcmp(renorm, 'gram'))&&(ry(i)*n(i)>5*(ry(i+1)+rz(i+1)))
                    [u,~,R]=svdgram(u);
                else
                    [u,R]=qr(u, 0);
                end;
                v = [v, zeros(ry(i+1)*M, size(crs,2))];
                v = v*R.';
                r = size(u, 2);
            end;
        end;
        y{i} = reshape(u, ry(i), n(i), r);

        cr2 = reshape(y{i+1}, ry(i+1), n(i+1)*ry(i+2));
        v = reshape(v, ry(i+1), M*r);
        cr2 = v.'*cr2;
        cr2 = reshape(cr2, M, r*n(i+1)*ry(i+2));
        y{i+1} = reshape(cr2.', r, n(i+1), ry(i+2), M);

        ry(i+1) = r;

        [phiyx(i+1,:), nrms(i)] = compute_next_Phi(phiyx(i,:), y{i}, X(i,:), 'lr', can);

        if (kickrank+kickrank2>0)
            rz(i+1) = size(crz, 2);
            z{i} = reshape(crz, rz(i), n(i), rz(i+1));
            % z{i+1} will be recomputed from scratch in the next step
            phizx(i+1,:) = compute_next_Phi(phizx(i,:), z{i}, X(i,:), 'lr', can, nrms(i));
            phizy(i+1) = compute_next_Phi(phizy(i), z{i}, y(i), 'lr');
        end;
    elseif ((dir<0)&&(i>1))
        cry = reshape(cry.', M*ry(i), n(i)*ry(i+1));
        if (M==1)&&(multrank)
            % Try to accelerate the rank growth
            cry = [cry; reshape(y{i}, ry(i), n(i)*ry(i+1))];
        end;
        [u,s,v]=svd(cry, 'econ');
        s = diag(s);
        r = my_chop2(s, tol*norm(s)/sqrt(d));
        r = min(r,rmax);
        u = u(:,1:r)*diag(s(1:r));
        v = conj(v(:,1:r));

        if (M==1)&&(multrank)
            % Remove auxiliary previous solution
            u = reshape(u, ry(i), 2, r);
            u = u(:,1,:);
            u = reshape(u, ry(i), r);
        end;

        % Prepare enrichment, if needed
        if (kickrank+kickrank2>0)
            cry = u*v.';
            cry = reshape(cry, M, ry(i)*n(i)*ry(i+1));
            % For updating z
            crys = cry.';
            crys = reshape(crys, ry(i), n(i)*ry(i+1)*M);
            crys = phizy{i}*crys;
            crys = reshape(crys, rz(i)*n(i)*ry(i+1), M);
            cryz = reshape(crys.', M*rz(i)*n(i), ry(i+1));
            cryz = cryz*phizy{i+1};
            cryz = reshape(cryz, M, rz(i)*n(i)*rz(i+1));
            cryz = cryz.';
            crz = proj_sum(phizx(i,:), X(i,:), phizx(i+1,:), c);
            crz = crz/nrms(i) - cryz;
            nrmz = norm(crz, 'fro');
            crz = reshape(crz.', M*rz(i), n(i)*rz(i+1));
            [~,~,crz]=svd(crz, 'econ');
            crz = crz(:, 1:min(size(crz,2), kickrank));
            if (kickrank2>0)
                crz = [crz, randn(n(i)*rz(i+1), kickrank2)];
                [crz,~]=qr(crz,0);
            end;
            crz = crz';
            % To add into solution
            crs = proj_sum(phizx(i,:), X(i,:), phiyx(i+1,:), c);
            crs = crs/nrms(i) - crys;
            crs = reshape(crs.', M*rz(i), n(i)*ry(i+1));
            [~,~,crs]=svd(crs, 'econ');
            crs = crs(:, 1:min(size(crs,2), kickrank));
            v = [v,conj(crs)];
            if (strcmp(renorm, 'gram'))&&(ry(i+1)*n(i)>5*(ry(i)+rz(i)))
                [v,~,R]=svdgram(v);
            else
                [v,R]=qr(v, 0);
            end;
            u = [u, zeros(M*ry(i), size(crs,2))];
            u = u*R.';
            r = size(v, 2);
        end;
        y{i} = reshape(v.', r, n(i), ry(i+1));

        cr2 = reshape(y{i-1}, ry(i-1)*n(i-1), ry(i));
        u = reshape(u, M, ry(i)*r);
        u = u.';
        u = reshape(u, ry(i), r*M);
        cr2 = cr2*u;
        cr2 = reshape(cr2, ry(i-1), n(i-1), r, M);
        y{i-1} = cr2;

        ry(i) = r;

        [phiyx(i,:), nrms(i)] = compute_next_Phi(phiyx(i+1,:), y{i}, X(i,:), 'rl', can);

        if (kickrank+kickrank2>0)
            rz(i) = size(crz, 1);
            z{i} = reshape(crz, rz(i), n(i), rz(i+1));
            % z{i+1} will be recomputed from scratch in the next step
            phizx(i,:) = compute_next_Phi(phizx(i+1,:), z{i}, X(i,:), 'rl', can, nrms(i));
            phizy(i) = compute_next_Phi(phizy(i+1), z{i}, y(i), 'rl');
        end;
    else
        y{i} = reshape(y{i}, ry(i), n(i), ry(i+1), M);
    end;

    if (verb>1)
        fprintf('amen-sum: swp=[%d,%d], dx=%3.3e, r=%d, |y|=%3.3e, |z|=%3.3e\n', swp, i, dx, r, norm(cry, 'fro'), nrmz);
    end;

    % Stopping or reversing
    if ((dir>0)&&(i==d))||((dir<0)&&(i==1))
        if (verb>0)
            fprintf('amen-sum: swp=%d{%d}, max_dx=%3.3e, max_r=%d\n', swp, (1-dir)/2, max_dx, max(ry));
        end;
        if ((max_dx<tol)||(swp==nswp))&&(dir>0)
            break;
        end;
        max_dx = 0;
        if (dir>0); swp = swp+1; end;
        dir = -dir;
    else
        i = i+dir;
    end;
end;

y{d} = reshape(cry, ry(d), n(d), M);

% Distribute norms equally...
nrms = exp(sum(log(nrms))/d);
% ... and plug them into y
for i=1:d
    y{i} = y{i}*nrms;
end;

if (vectype==1)
    y = cell2core(tt_tensor, y);
    z = cell2core(tt_tensor, z);
end;

end

function [Phi,nrm] = compute_next_Phi(Phi_prev, x, Y, direction, can, extnrm)
% Performs the recurrent Phi (or Psi) matrix computation
% Phi = Phi_prev * (x'y).
% If direction is 'lr', computes Psi
% if direction is 'rl', computes Phi
% A can be empty, then only x'y is computed.

% Phi1:  rx1, ry1
% Phi2:  ry2, rx2

if (nargin<5)||(isempty(can))
    can = false;
end;
if (nargin<6)
    extnrm = [];
end;

rx1 = size(x,1); n = size(x,2); rx2 = size(x,3);
N = numel(Y);
if (can) % We are working with the canonical format
    Phi = cell(1,1);
    R = size(Y{1},2);
    % Phi1: rx1, R
    % Phi2: R, rx2

    if (strcmp(direction, 'lr'))
        %lr: Phi1
        x = reshape(x, rx1, n*rx2);
        Phi{1} = x'*Phi_prev{1};
        Phi{1} = reshape(Phi{1}, n*rx2, R);
        Y = repmat(Y{1}, rx2, 1); % equalize the sizes for Hadamard product
        Phi{1} = Phi{1}.*Y;
        Phi{1} = reshape(Phi{1}, n, rx2*R);
        Phi{1} = sum(Phi{1}, 1);
        Phi{1} = reshape(Phi{1}, rx2, R);
        if (nargout>1)
            % Extract the scale to prevent overload
            nrm = norm(Phi{1}, 'fro');
            if (nrm>0)
                Phi{1} = Phi{1}/nrm;
            else
                nrm=1;
            end;
        elseif (~isempty(extnrm))
            % Override the normalization by the external one
            Phi{1} = Phi{1}/extnrm;
        end;
    else
        %rl: Phi2
        x = reshape(x, rx1*n, rx2);
        Phi{1} = Phi_prev{1}*x';
        Phi{1} = reshape(Phi{1}, R, rx1*n);
        Y = Y{1}.';
        Y = repmat(Y,rx1,1); % equalize the sizes for Hadamard product
        Y = reshape(Y, R, rx1*n);
        Phi{1} = Phi{1}.*Y;
        Phi{1} = reshape(Phi{1}, R*rx1, n);
        Phi{1} = sum(Phi{1}, 2);
        Phi{1} = reshape(Phi{1}, R, rx1);
        if (nargout>1)
            % Extract the scale to prevent overload
            nrm = norm(Phi{1}, 'fro');
            if (nrm>0)
                Phi{1} = Phi{1}/nrm;
            else
                nrm=1;
            end;
        elseif (~isempty(extnrm))
            % Override the normalization by the external one
            Phi{1} = Phi{1}/extnrm;
        end;
    end;
else % a set of TT-tensors
    Phi = cell(1,N);
    if (strcmp(direction, 'lr'))
        %lr: Phi1
        x = reshape(x, rx1, n*rx2);
        if (nargout>1)
            nrm = 0;
        end;
        for i=1:N
            y = Y{i};
            ry1 = size(y,1); ry2 = size(y,3);
            Phi{i} = x'*Phi_prev{i};
            Phi{i} = reshape(Phi{i}, n, rx2*ry1);
            Phi{i} = Phi{i}.';
            Phi{i} = reshape(Phi{i}, rx2, ry1*n);
            y = reshape(y, ry1*n, ry2);
            Phi{i} = Phi{i}*y;
            Phi{i} = reshape(Phi{i}, rx2, ry2);
            if (nargout>1)
                nrm = max(nrm, norm(Phi{i}, 'fro'));
            end;
        end;
        if (nargout>1)
            % Extract the scale to prevent overload
            if (nrm>0)
                for i=1:N
                    Phi{i} = Phi{i}/nrm;
                end;
            else
                nrm=1;
            end;
        elseif (~isempty(extnrm))
            % Override the normalization
            for i=1:N
                Phi{i} = Phi{i}/extnrm;
            end;
        end;
    else
        %rl: Phi2
        x = reshape(x, rx1, n*rx2);
        if (nargout>1)
            nrm = 0;
        end;
        for i=1:N
            y = Y{i};
            ry1 = size(y,1); ry2 = size(y,3);
            y = reshape(y, ry1*n, ry2);
            Phi{i} = y*Phi_prev{i};
            Phi{i} = reshape(Phi{i}, ry1, n*rx2);
            Phi{i} = Phi{i}*x';
            Phi{i} = reshape(Phi{i}, ry1, rx1);
            if (nargout>1)
                nrm = max(nrm, norm(Phi{i}, 'fro'));
            end;
        end;
        if (nargout>1)
            % Extract the scale to prevent overload
            if (nrm>0)
                for i=1:N
                    Phi{i} = Phi{i}/nrm;
                end;
            else
                nrm=1;
            end;
        elseif (~isempty(extnrm))
            % Override the normalization
            for i=1:N
                Phi{i} = Phi{i}/extnrm;
            end;
        end;
    end;
end;

end

function [x,r]=gen_rand(n,d,r)
% Generate an orthogonal random vector
if (numel(r)==1)
    r = [1; r*ones(d-1,1); 1];
end;
x = cell(d,1);
for i=1:d
    cr = randn(r(i)*n(i), r(i+1));
    [cr,~]=qr(cr,0);
    r(i+1) = size(cr,2);
    x{i} = reshape(cr, r(i), n(i), r(i+1));
end;
end

function [y]=proj_sum(Phi1, X, Phi2, c)
N = size(c,1);
M = size(c,2);

ry1 = size(Phi1{1},1);
ry2 = size(Phi2{1},2);

if (numel(X)==1) % Canonical format
    X = X{1};
    n = size(X,1);
    Phi1 = repmat(Phi1{1}, n, 1); % size ry1*n, R
    X = reshape(X,  1, n*N);
    X = repmat(X, ry1, 1);
    X = reshape(X, ry1*n, N);
    y = X.*Phi1;
    c = repmat(c, ry2, 1); % size N*ry2, M
    c = reshape(c, N, ry2*M);
    Phi2 = repmat(Phi2{1}, 1, M); % size N, ry2*M
    Phi2 = Phi2.*c;
    y = y*Phi2;
    y = reshape(y, ry1*n*ry2, M);
    if (issparse(y))
        y = full(y);
    end;
else
    n = size(X{1}, 2);
    y = zeros(ry1*n*ry2, M);
    for j=1:N
        rx1 = size(X{j},1); rx2 = size(X{j},3);
        cry = reshape(X{j}, rx1, n*rx2);
        cry = Phi1{j}*cry;
        cry = reshape(cry, ry1*n, rx2);
        cry = cry*Phi2{j};
        cry = reshape(cry, ry1*n*ry2, 1);
        cry = cry*c(j,:);
        y = y+cry;
    end;
end;
end