function [model, metProduction, essentialRxnsForTasks, addedRxnsForTasks, deletedDeadEndRxns, deletedRxnsInINIT, taskReport]=getINITModel(refModel, tissue, celltype, hpaData, arrayData, metabolomicsData, taskFile, useScoresForTasks, printReport, taskStructure, params, paramsFT)
% getINITModel_legacy
%   Generates a model using the INIT algorithm, based on proteomics and/or
%   transcriptomics and/or metabolomics and/or metabolic tasks. This is the original
%   implementation of tINIT, which is replaced by ftINIT.
%
%   Input:
%   refModel            a model structure. The model should be in the
%                       closed form (no exchange reactions open). Import
%                       using import(filename,false). If the model is not
%                       loaded using importModel, it might be that there
%                       is no "unconstrained" field. In that case,
%                       manually add the field like:
%                       model.unconstrained=false(numel(model.mets),1);
%   tissue              tissue to score for. Should exist in either
%                       hpaData.tissues or arrayData.tissues
%   celltype            cell type to score for. Should exist in either
%                       hpaData.celltypes or arrayData.celltypes for this
%                       tissue (opt, default is to use the best values
%                       among all the cell types for the tissue. Use [] if
%                       you want to supply more arguments)
%   hpaData             HPA data structure from parseHPA (opt if arrayData is
%                       supplied, default [])
%   arrayData           gene expression data structure (opt if hpaData is
%                       supplied, default [])
%       genes           cell array with the unique gene names
%       tissues         cell array with the tissue names. The list may not be
%                       unique, as there can be multiple cell types per tissue
%       celltypes       cell array with the cell type names for each tissue
%       levels          GENESxTISSUES array with the expression level for
%                       each gene in each tissue/celltype. NaN should be
%                       used when no measurement was performed
%       threshold       a single value or a vector of gene expression
%                       thresholds, above which genes are considered to be
%                       "expressed". (opt, by default, the mean expression
%                       levels of each gene across all tissues in arrayData
%                       will be used as the threshold values)
%       singleCells     binary value selecting whether to use the
%                       single-cell algorithm to identify expressed genes.
%                       If used, specify cell subpopulations in CELLTYPES
%                       (opt, default [])
%       plotResults     true if single cell probability distributions
%                       should be plotted (opt, default = False)
%   metabolomicsData    cell array with metabolite names that the model
%                       should produce (opt, default [])
%   taskFile            a task list in Excel format. See parseTaskList for
%                       details (opt, default [])
%   useScoresForTasks   true if the calculated reaction scored should be used as
%                       weights in the fitting to tasks (opt, default true)
%   printReport         true if a report should be printed to the screen
%                       (opt, default true)
%   taskStructure       task structure as from parseTaskList. Can be used
%                       as an alternative way to define tasks when Excel
%                       sheets are not suitable. Overrides taskFile (opt,
%                       default [])
%   params              parameter structure as used by getMILPParams. This is
%                       for the INIT algorithm. For the the MILP problems
%                       solved to fit tasks, see paramsFT (opt, default [])
%   paramsFT            parameter structure as used by getMILPParams. This is
%                       for the fitTasks step. For the INIT algorithm, see
%                       params (opt, default [])
%
%
%   Output:
%   model                   the resulting model structure
%   metProduction           array that indicates which of the
%                           metabolites in metabolomicsData that could be
%                           produced. Note that this is before the
%                           gap-filling process to enable defined tasks. To
%                           see which metabolites that can be produced in
%                           the final model, use canProduce.
%                           -2: metabolite name not found in model
%                           -1: metabolite found, but it could not be produced
%                           1: metabolite could be produced
%   essentialRxnsForTasks   cell array of the reactions which were
%                           essential to perform the tasks
%   addedRxnsForTasks       cell array of the reactions which were added in
%                           order to perform the tasks
%   deletedDeadEndRxns      cell array of reactions deleted because they
%                           could not carry flux (INIT requires a
%                           functional input model)
%   deletedRxnsInINIT       cell array of the reactions which were deleted by
%                           the INIT algorithm
%   taskReport              structure with the results for each task
%   	id                  cell array with the id of the task
%       description         cell array with the description of the task
%       ok                  boolean array with true if the task was successful
%       essential           cell array with cell arrays of essential
%                           reactions for the task
%       gapfill             cell array of cell arrays of reactions included
%                           in the gap-filling for the task
%
%   This is the main function for automatic reconstruction of models based
%   on the (t)INIT algorithm (PLoS Comput Biol. 2012;8(5):e1002518,
%   Mol Syst Biol. 2014;10:721). Not all settings are possible using this
%   function, and you may want to call the functions scoreModel, runINIT
%   and fitTasks individually instead.
%
%   NOTE: Exchange metabolites should normally not be removed from the model
%   when using this approach, since checkTasks/fitTasks rely on putting specific
%   constraints for each task. The INIT algorithm will remove exchange metabolites
%   if any are present. Use importModel(file,false) to import a model with
%   exchange metabolites remaining.
%
%   Usage: [model, metProduction, essentialRxnsForTasks, addedRxnsForTasks,...
%               deletedDeadEndRxns, deletedRxnsInINIT, taskReport]=...
%               getINITModel(refModel, tissue, celltype, hpaData, arrayData,...
%               metabolomicsData, taskFile, useScoresForTasks, printReport,...
%               taskStructure, params, paramsFT)

if nargin<3
    celltype=[];
else
    celltype=char(celltype);
end
if nargin<4
    hpaData=[];
end
if nargin<5
    arrayData=[];
end
if nargin<6
    metabolomicsData=[];
end
if nargin<7
    taskFile=[];
else
    taskFile=char(taskFile);
end
if nargin<8 || isempty(useScoresForTasks)
    useScoresForTasks=true;
end
if nargin<9 || isempty(printReport)
    printReport=true;
end
if nargin<10
    taskStructure=[];
end
if nargin<11
    params=[];
end
if nargin<12
    paramsFT=[];
end

%Check that the model is in the closed form
if ~isfield(refModel,'unconstrained')
    EM='Exchange metabolites should normally not be removed from the model when using getINITModel. Use importModel(file,false) to import a model with exchange metabolites remaining (see the documentation for details)';
    dispEM(EM);
end

%Create the task structure if not supplied
if any(taskFile) && isempty(taskStructure)
    taskStructure=parseTaskList(taskFile);
end

% sc-tINIT to identify confidence levels of gene expression
if ~isempty(arrayData) && isfield(arrayData,'singleCells')
    if arrayData.singleCells == 1
        % Check to ensure cell type is defined
        if ~isfield(arrayData,'celltypes')
            dispEM('arrayData must contain cell type information if sc-tINIT is to be used','false');
        end
        if ~ismember(upper(celltype),upper(arrayData.celltypes))
            dispEM('The cell type name does not match');
        end

        % Analyze only cell type of interest
        J= strcmpi(arrayData.celltypes,celltype);

        % Analyze only genes included in the reference model
        I=ismember(arrayData.genes,refModel.genes);

        % Convert expression data to population fractions
        binary_levels = arrayData.levels(I,J)~=0; % Classify each gene as detected (1) or not (0) in each cell
        cell_count_levels = sum(binary_levels,2); % Number of cells expressing each transcript
        cell_frac_levels = cell_count_levels/size(binary_levels,2); % Number of cells expressing each transcript

        % Bin cell_frac_counts manually
        x = 0:.01:1;
        for(i = 1:length(x))
            cell_frac_count(i) = sum(round(cell_frac_levels,2)==x(i));
        end

        % Fit four beta distributions
        cell_frac_count(cell_frac_count==0) = NaN; % Remove zeros from optimization
        cell_frac_count(1) = NaN; % Remove non-expressed genes from optimization
        x_lim = 1; % Somewhat arbitrary parameter to fit left tail of distr.
        myfun = @(par) nansum((cell_frac_count(1:find(x>=x_lim,1)) - ...
            abs(par(1))*betapdf(x(1:find(x>=x_lim,1)),abs(par(2)),abs(par(3))) - ...
            abs(par(4))*betapdf(x(1:find(x>=x_lim,1)),abs(par(5)),abs(par(6))) - ...
            abs(par(7))*betapdf(x(1:find(x>=x_lim,1)),abs(par(8)),abs(par(9))) - ...
            abs(par(10))*betapdf(x(1:find(x>=x_lim,1)),abs(par(11)),abs(par(12)))).^2);

        par0 = [4,2,100,7,2,30,7,5,20,5,15,20];
        opts = optimset('Display','off');
        [par,f_val] = fminsearch(myfun,par0,opts);
        par = abs(par);

        % Plot results
        if (isfield(arrayData,'plotResults'))
            if arrayData.plotResults == true
                figure(); hold on; plot(x,cell_frac_count,'ko','MarkerSize',5);
                plot(x,abs(par(1))*betapdf(x,abs(par(2)),abs(par(3))),'b-','LineWidth',1)
                plot(x,abs(par(4))*betapdf(x,abs(par(5)),abs(par(6))),'b-','LineWidth',1)
                plot(x,abs(par(7))*betapdf(x,abs(par(8)),abs(par(9))),'b-','LineWidth',1)
                plot(x,abs(par(10))*betapdf(x,abs(par(11)),abs(par(12))),'b-','LineWidth',1)
                plot(x,abs(par(1))*betapdf(x,abs(par(2)),abs(par(3))) + ...
                    abs(par(4))*betapdf(x,abs(par(5)),abs(par(6))) + ...
                    abs(par(7))*betapdf(x,abs(par(8)),abs(par(9))) + ...
                    abs(par(10))*betapdf(x,abs(par(11)),abs(par(12))),'-','Color',[.5 .5 .5],'LineWidth',2)
                xlabel('Expression Probability');ylabel('# of genes');set(gca,'FontSize',14,'LineWidth',1.25);
                title('Expression prediction','FontSize',18,'FontWeight','bold')
            end
        end

        % Score genes based on population expression (p = .05)
        exprs_cutoff_1 = find(cdf('beta',x,par(2),par(3)) >.95,1)-1; % Find index of no confidence genes
        exprs_cutoff_2 = find(cdf('beta',x,par(5),par(6)) >.95,1)-1; % Find index of low confidence genes
        exprs_cutoff_3 = find(cdf('beta',x,par(8),par(9)) >.95,1)-1; % Find index of low confidence genes
        exprs_cutoffs = sort([exprs_cutoff_1,exprs_cutoff_2,exprs_cutoff_3]);
        gene_scores = cell_frac_levels*0;
        gene_scores(cell_frac_levels <= x(exprs_cutoffs(1))) = 4; % Not detected
        gene_scores(logical((cell_frac_levels >= x(exprs_cutoffs(1))).*(cell_frac_levels < x(exprs_cutoffs(2))))) = 3; % Low detection
        gene_scores(logical((cell_frac_levels >= x(exprs_cutoffs(2))).*(cell_frac_levels < x(exprs_cutoffs(3))))) = 2; % Medium detection
        gene_scores(cell_frac_levels > x(exprs_cutoffs(3))) = 1; % High detection

        % Replace hpaData with singleCellData
        if printReport==true
            dispEM('Single cell data is not currently compatible with HPA data. \n         Replacing hpaData with single cell-based scoring.',false);
        end
        hpaData.genes = arrayData.genes;
        hpaData.tissues = arrayData.tissues;
        hpaData.celltypes = arrayData.celltypes;
        hpaData.levels = [{'High'},{'Medium'},{'Low'},{'None'}];
        hpaData.gene2Level = zeros(length(arrayData.genes),length(arrayData.celltypes));
        for i = 1:length(find(J))
            find_var = find(J,i);
            hpaData.gene2Level(I,find_var(end)) = gene_scores;
        end

        % Remove arrayData from the analysis (Might be a bad idea)
        clear arrayData
        arrayData=[];
    end
end

if printReport==true
    if any(celltype)
        fprintf(['***Generating model for: ' tissue ' - ' celltype '\n']);
    else
        fprintf(['***Generating model for: ' tissue '\n']);
    end
    if ~isempty(hpaData)
        fprintf('-Using HPA data\n');
    end
    if ~isempty(arrayData)
        fprintf('-Using array data\n');
    end
    if ~isempty(metabolomicsData)
        fprintf('-Using metabolomics data\n');
    end
    if ~isempty(taskFile) || ~isempty(taskStructure)
        fprintf('-Using metabolic tasks\n');
    end
    fprintf('\n');

    printScores(refModel,'Reference model statistics',hpaData,arrayData,tissue,celltype);
end

%Remove dead-end reactions to speed up the optimization and to
%differentiate between reactions removed by INIT and those that are
%dead-end
[~, deletedDeadEndRxns]=simplifyModel(refModel,true,false,true,true,true);
cModel=removeReactions(refModel,deletedDeadEndRxns,false,true);

%Store the connected model like this to keep track of stuff
if printReport==true
    printScores(cModel,'Pruned model statistics',hpaData,arrayData,tissue,celltype);
end

%If tasks have been defined, then go through them and get essential
%reactions
if ~isempty(taskStructure)
    [taskReport, essentialRxnMat]=checkTasks(cModel,[],printReport,true,true,taskStructure);

    essentialRxnsForTasks=cModel.rxns(any(essentialRxnMat,2));

    %Remove tasks that cannot be performed
    taskStructure(taskReport.ok==false)=[];
    if printReport==true
        printScores(removeReactions(cModel,setdiff(cModel.rxns,essentialRxnsForTasks),true,true),'Reactions essential for tasks',hpaData,arrayData,tissue,celltype);
    end
else
    essentialRxnsForTasks={};
end

%Score the connected model
[rxnScores, geneScores]=scoreModel(cModel,hpaData,arrayData,tissue,celltype);

%Run the INIT algorithm. The exchange reactions that are used in the final
%reactions will be open, which doesn't fit with the last step. Therefore
%delete reactions from the original model instead of taking the output. The
%default implementation does not constrain reversible reactions to only
%carry flux in one direction. Runs without the constraints on reversibility
%and with all output allowed. This is to reduce the complexity of the
%problem.
[~, deletedRxnsInINIT, metProduction]=runINIT(simplifyModel(cModel),rxnScores,metabolomicsData,essentialRxnsForTasks,0,true,false,params);
initModel=removeReactions(cModel,deletedRxnsInINIT,true,true);
if printReport==true
    printScores(initModel,'INIT model statistics',hpaData,arrayData,tissue,celltype);
    printScores(removeReactions(cModel,setdiff(cModel.rxns,deletedRxnsInINIT),true,true),'Reactions deleted by INIT',hpaData,arrayData,tissue,celltype);
end

%The full model has exchange reactions in it. fitTasks calls on fillGaps,
%which automatically removes exchange metabolites (because it assumes that
%the reactions are constrained when appropriate). In this case the
%uptakes/outputs are retrieved from the task sheet instead. To prevent
%exchange reactions being used to fill gaps, they are delete from the
%reference model here.
initModel.id='INITModel';

%If gaps in the model should be filled using a task list
if ~isempty(taskStructure)
    %Remove exchange reactions and reactions already included in the INIT
    %model
    refModelNoExc=removeReactions(refModel,union(initModel.rxns,getExchangeRxns(refModel)),true,true);

    %At this stage the model is fully connected and most of the genes with
    %good scores should have been included. The final gap-filling should
    %take the scores of the genes into account, so that "rather bad"
    %reactions are preferred to "very bad" reactions. However, reactions
    %with positive scores will be included even if they are not connected
    %in the current formulation. Therefore, such reactions will have to be
    %assigned a small negative score instead.
    if useScoresForTasks==true
        refRxnScores=scoreModel(refModelNoExc,hpaData,arrayData,tissue,celltype);
        [outModel, addedRxnMat]=fitTasks(initModel,refModelNoExc,[],true,min(refRxnScores,-0.1),taskStructure,paramsFT);
    else
        [outModel, addedRxnMat]=fitTasks(initModel,refModelNoExc,[],true,[],taskStructure,paramsFT);
    end
    if printReport==true
        printScores(outModel,'Functional model statistics',hpaData,arrayData,tissue,celltype);
        printScores(removeReactions(outModel,intersect(outModel.rxns,initModel.rxns),true,true),'Reactions added to perform the tasks',hpaData,arrayData,tissue,celltype);
    end

    addedRxnsForTasks=refModelNoExc.rxns(any(addedRxnMat,2));
else
    outModel=initModel;
    addedRxnMat=[];
    addedRxnsForTasks={};
end

%The model can now perform all the tasks defined in the task list. The
%algorithm cannot deal with gene-complexes at the moment. It is therefore
%ok to remove bad genes from a reaction (as long as at least one gene is
%kept)
model=outModel;

[~, I]=ismember(model.genes,cModel.genes); %All should be found
%This is a little weird way to make sure that only one bad gene is included
%if there are no good ones (since all -Inf==max(-Inf))
geneScores(isinf(geneScores))=-1000+rand(sum(isinf(geneScores)),1);

model.grRules(:)={''};
for i=1:numel(model.rxns)
    ids=find(model.rxnGeneMat(i,:));
    if numel(ids)>1
        scores=geneScores(I(ids));
        %Only keep the positive ones if possible
        model.rxnGeneMat(i,ids(~(scores>0 | scores==max(scores))))=0;
    end
    %Rewrite the grRules to be only OR
    if isfield(model,'grRules')
        J=find(model.rxnGeneMat(i,:));
        for j=1:numel(J)
            model.grRules{i}=[model.grRules{i} '(' model.genes{J(j)} ')'];
            if j<numel(J)
                model.grRules{i}=[model.grRules{i} ' or '];
            end
        end
    end
end

%Find all genes that are not used and delete them
I=sum(model.rxnGeneMat)==0;
model.genes(I)=[];
model.rxnGeneMat(:,I)=[];
if isfield(model,'geneShortNames')
    model.geneShortNames(I)=[];
end
if isfield(model,'geneMiriams')
    model.geneMiriams(I)=[];
end
if isfield(model,'geneFrom')
    model.geneFrom(I)=[];
end
if isfield(model,'geneComps')
    model.geneComps(I)=[];
end

%At this stage the model will contain some exchange reactions but probably
%not all (and maybe zero). This can be inconvenient, so all exchange
%reactions from the reference model are added, except for those which
%involve metabolites that are not in the model.

%First delete and included exchange reactions in order to prevent the order
%from changing
model=removeReactions(model,getExchangeRxns(model));

%Create a model with only the exchange reactions in refModel
excModel=removeReactions(refModel,setdiff(refModel.rxns,getExchangeRxns(refModel)),true,true);

%Find the metabolites there which are not exchange metabolites and which do
%not exist in the output model
I=~ismember(excModel.mets,model.mets) & excModel.unconstrained==0;

%Then find those reactions and delete them
[~, J]=find(excModel.S(I,:));
excModel=removeReactions(excModel,J,true,true);

%Merge with the output model
model=mergeModels({model;excModel},'metNames');
model.id='INITModel';
model.name=['Automatically generated model for ' tissue];
if any(celltype)
    model.name=[model.name ' - ' celltype];
end

if printReport==true
    printScores(model,'Final model statistics',hpaData,arrayData,tissue,celltype);
end

%Add information about essential reactions and reactions included for
%gap-filling and return a taskReport
if ~isempty(taskStructure)
    I=find(taskReport.ok); %Ignore failed tasks
    for i=1:numel(I)
        taskReport.essential{I(i),1}=cModel.rxns(essentialRxnMat(:,I(i)));
        taskReport.gapfill{I(i),1}=refModelNoExc.rxns(addedRxnMat(:,i));
    end
else
    taskReport=[];
end

%Fix grRules and reconstruct rxnGeneMat
[grRules,rxnGeneMat] = standardizeGrRules(model,true);
model.grRules = grRules;
model.rxnGeneMat = rxnGeneMat;
end

%This is for printing a summary of a model
function [rxnS, geneS]=printScores(model,name,hpaData,arrayData,tissue,celltype)
[a, b]=scoreModel(model,hpaData,arrayData,tissue,celltype);
rxnS=mean(a);
geneS=mean(b(~isinf(b)));
fprintf([name ':\n']);
fprintf(['\t' num2str(numel(model.rxns)) ' reactions, ' num2str(numel(model.genes)) ' genes\n']);
fprintf(['\tMean reaction score: ' num2str(rxnS) '\n']);
fprintf(['\tMean gene score: ' num2str(geneS) '\n']);
fprintf(['\tReactions with positive scores: ' num2str(100*sum(a>0)/numel(a)) '%%\n\n']);
end


function [struct_irf_record D_record gamma_record]=mairfres(beta_gibbs,sigma_gibbs,It,Bu,IRFperiods,n,p,k1,signrestable,signresperiods)

% function [struct_irf_record D_record gamma_record Qdraw Qsuccess]=mairfres(beta_gibbs,sigma_gibbs,It,Bu,IRFperiods,n,p,k1,signrestable,signresperiods)
% runs the gibbs sampler to obtain draws from the posterior distribution of IRFs, orthogonalised with a sign restriction setting
% inputs:  - matrix 'beta_gibbs': record of the gibbs sampler draws for the beta vector
%          - matrix 'sigma_gibbs': record of the gibbs sampler draws for the sigma matrix (vectorised)
%          - integer 'It': total number of iterations of the Gibbs sampler (defined p 28 of technical guide)
%          - integer 'Bu': number of burn-in iterations of the Gibbs sampler (defined p 28 of technical guide)
%          - integer 'IRFperiods': number of periods for IRFs
%          - integer 'n': the number of endogenous variables in the model
%          - integer 'p': number of lags included in the model (defined p 7 of technical guide)
%          - integer 'k1': the number of coefficients related to the endogenous variables for each equation in the model
%          - cell 'signrestable': table recording the sign restriction input from the user
%          - cell 'signresperiods': table containing the periods corresponding to each restriction
% outputs: - cell 'struct_irf_record': record of the gibbs sampler draws for the orthogonalised IRFs
%          - matrix 'D_record': record of the gibbs sampler draws for the structural matrix D
%          - matrix 'gamma_record': record of the gibbs sampler draws for the structural disturbances variance-covariance matrix gamma
%          - integer 'Qdraw': total number of draws of the Q matrix
%          - integer 'Qsuccess': number of successful draws of the Q matrix

% preliminary tasks
% create first the cell that will store the results from the simulations
struct_irf_record=cell(n,n);
% storage cell
storage1=cell(It-Bu,1);
storage2=cell(It-Bu,1);

% now identify all the periods concerned with restrictions
% first expand the non-empty entries in signresperiods since they are only expressed in intervals: transform into list
% for instance, translate [1 4] into [1 2 3 4]; I don't think this can done without a loop
temp=cell2mat(signresperiods(~cellfun(@isempty,signresperiods)));
periods=[];
for ii=1:size(temp,1)
periods=[periods temp(ii,1):temp(ii,2)];
end
% suppress duplicates and sort
periods=sort(unique(periods))';
% count the total number of restriction periods (required for IRF matrix)
nperiods=size(periods,1);

% Identify the restriction matrices
% create five cells, corresponding to the three possible restrictions:
% one cell for sign restrictions, three cells for magnitude restrictions, one cell for zero restrictions
Scell=cell(1,n);
Mcell=cell(1,n);
Mlcell=cell(1,n);
Mucell=cell(1,n);
Zcell=cell(1,n);

% Check if value and periods restrictions correspond to each other
if sum(sum(~cellfun(@isempty,signresperiods) == ~cellfun(@isempty,signrestable))) == n^2
    % All cells with sign restrictions also specify the horizon over which
    % these are applied
else
    disp('Warning: Value restrictions do not correspond to period restrictions one to one')
    pause(1)
end

% loop over rows and columns of the period matrix
for ii=1:n
   for jj=1:n
      % if entry (ii,jj) of the period matrix is not empty...
      if ~isempty(signresperiods{ii,jj}) && ~isempty(signrestable{ii,jj})
      % ... then there is a restriction over one (or several) periods
      % loop overt those periods
         for kk=signresperiods{ii,jj}(1,1):signresperiods{ii,jj}(1,2)
         % identify the position of the considered period within the list of all periods (required to build the matrix)
         position=find(periods==kk);
         % now create the restriction matrix: this will depend on the type of restriction
            % if it is a positive sign restriction...
            if strcmp(signrestable{ii,jj},'+')
            % ... then input a 1 entry in the corresponding S matrix
            Scell{1,jj}=[Scell{1,jj};zeros(1,n*nperiods)];
            Scell{1,jj}(end,(position-1)*n+ii)=1;
            % if it is a negative sign restriction...
            elseif strcmp(signrestable{ii,jj},'-')
            % ... then input a -1 entry in the corresponding S matrix
            Scell{1,jj}=[Scell{1,jj};zeros(1,n*nperiods)];
            Scell{1,jj}(end,(position-1)*n+ii)=-1;
            % if it is a zero restriction...
            elseif strcmp(signrestable{ii,jj},'0')
            % ... then input a 1 entry in the corresponding Z matrix
            Zcell{1,jj}=[Zcell{1,jj};zeros(1,n*nperiods)];
            Zcell{1,jj}(end,(position-1)*n+ii)=1;
            % else, a non-empty entry being neither a sign nor a zero restriction has to be a magnitude restriction
            else
            % fill the corresponding M matrices:
            % input a 1 in M
            Mcell{1,jj}=[Mcell{1,jj};zeros(1,n*nperiods)];
            Mcell{1,jj}(end,(position-1)*n+ii)=1;
            % input the lower value of the interval in Ml
            temp=str2num(signrestable{ii,jj});
            Mlcell{1,jj}=[Mlcell{1,jj};temp(1,1)];
            % input the upper value of the interval in Mu
            Mucell{1,jj}=[Mucell{1,jj};temp(1,2)];
            end
         end
      end
   end
end

% now check what kind of restrictions apply among sign, zero and magnitude restrictions
% check for sign restrictions: if there are any, at least one entry in the cell Scell is non-empty
if sum(~cellfun(@isempty,Scell))~=0
signres=1;
else
signres=0;
end
% similarly check for zero restrictions
if sum(~cellfun(@isempty,Zcell))~=0
zerores=1;
else
zerores=0;
end
% and finally, check for magnitude restrictions
if sum(~cellfun(@isempty,Mcell))~=0
magnres=1;
else
magnres=0;
end

not_successful = 0;
hbar = bear.parfor_progressbar(It-Bu,'Progress of Sign Restriction Draws');  %create the progress bar

% initiate Gibbs algorithm
parfor ii=1:It-Bu
% initiate the variable 'success'; this variable will be used to check whether the restrictions are satisfied
success=0;

   while success==0
   not_successful = not_successful+1;
   % switch 'success' to 1; it will be turned back to zero if at any time Q is detected as a candidate not satisfying the restrictions
   success=1;

   % draw the vector of VAR coefficients
   % select first a draw index randomly
   index=floor(rand*(It-Bu))+1;
   % then draw a random set of beta and sigma corresponding to this index (this is done to make it possible to draw, if required, an infinite number of values from the gibbs sampler record, with equal probability on each value)
   B=reshape(beta_gibbs(:,ii),k1,n);
   sigma=reshape(sigma_gibbs(:,ii),n,n);
   hsigma=chol(bear.nspd(sigma),'lower');
   % obtain orthogonalised IRFs
   [irfmatrix ortirfmatrix]=bear.mairfsim(B,hsigma,p,n,max(IRFperiods,max(periods)));

   % generate the stacked IRF matrix
   stackedirfmat=[];
      for jj=1:numel(periods)
      stackedirfmat=[stackedirfmat;ortirfmatrix(:,:,periods(jj,1)+1)];
      end
   % draw an entire random matrix Q satisfying the zero restrictions
   [Q]=bear.qzerores(n,Zcell,stackedirfmat);

   % generate the candidate matrix of structural IRFs
   candidate=stackedirfmat*Q;

   % verify the restrictions in turn (sign, magnitude, or both)
   % the zero restrictions don't have to be verified, there are satisfied by construction
      % consider first the case of pure sign restrictions
      if signres==1 && magnres==0
         % loop over structural shocks; stop as soon as the draw is detected as a fail
         jj=1;
         while success==1 && jj<=n
            % if the corresponding entry in Scell is not empty, it contains a restriction to be checked
            if ~isempty(Scell{1,jj})
               % check if the restrictions hold
               if all(Scell{1,jj}*candidate(:,jj)>=0)
               % if the restrictions do not hold, there may still be a possibility by switching the sign of the Q column
               elseif all(Scell{1,jj}*((-1)*candidate(:,jj))>=0)
               Q(:,jj)=-Q(:,jj);
               % else, if there is no way to have Q succesful, count it as a fail and switch the variable success to 0
               else
               success=0;
               end
            end
         jj=jj+1;
         end
      % consider now the case of pure magnitude restrictions
      elseif signres==0 && magnres==1
         % loop over structural shocks
         jj=1;
         while success==1 && jj<=n
            % if the corresponding entry is not empty, it contains a restriction to be checked
            if ~isempty(Mcell{1,jj})
               % check if the restriction holds
               if all((Mcell{1,jj}*candidate(:,jj)-Mlcell{1,jj}).*(Mucell{1,jj}-Mcell{1,jj}*candidate(:,jj))>=0)
               % if they do not hold, there may still be a possibility by switching the sign of the Q column
               elseif all((Mcell{1,jj}*(-1)*candidate(:,jj)-Mlcell{1,jj}).*(Mucell{1,jj}-Mcell{1,jj}*(-1)*candidate(:,jj))>=0)
               Q(:,jj)=-Q(:,jj);
               % else, if there is no way to have Q succesful, count it as a fail and switch the variable success to 0
               else
               success=0;
               end
            end
         jj=jj+1;
         end
      % consider then the case of mixed sign and magnitude restrictions
      elseif signres==1 && magnres==1
         % loop over structural shocks
         jj=1;
         while success==1 && jj<=n
         % for a given structural shock, there may be no restrictions, only one type, or both types
            % if both types
            if ~isempty(Scell{1,jj}) && ~isempty(Mcell{1,jj})
               % check both restrictions
               if all(Scell{1,jj}*candidate(:,jj)>=0) && all((Mcell{1,jj}*candidate(:,jj)-Mlcell{1,jj}).*(Mucell{1,jj}-Mcell{1,jj}*candidate(:,jj))>=0)
               % if not, try to switch the sign of the corresponding column in Q
               elseif all(Scell{1,jj}*((-1)*candidate(:,jj))>=0) && all((Mcell{1,jj}*(-1)*candidate(:,jj)-Mlcell{1,jj}).*(Mucell{1,jj}-Mcell{1,jj}*(-1)*candidate(:,jj))>=0)
               Q(:,jj)=-Q(:,jj);
               % else, if there is no way to have Q succesful, count it as a fail and switch the variable success to 0
               else
               success=0;
               end
            % if only sign restrictions
            elseif ~isempty(Scell{1,jj}) && isempty(Mcell{1,jj})
               % check if the restrictions hold
               if all(Scell{1,jj}*candidate(:,jj)>=0)
               % if the restrictions do not hold, there may still be a possibility by switching the sign of the Q column
               elseif all(Scell{1,jj}*((-1)*candidate(:,jj))>=0)
               Q(:,jj)=-Q(:,jj);
               % else, if there is no way to have Q succesful, count it as a fail and switch the variable success to 0
               else
               success=0;
               end
            % if only magnitude restrictions
            elseif isempty(Scell{1,jj}) && ~isempty(Mcell{1,jj})
               % check if the restriction holds
               if all((Mcell{1,jj}*candidate(:,jj)-Mlcell{1,jj}).*(Mucell{1,jj}-Mcell{1,jj}*candidate(:,jj))>=0)
               % if they do not hold, there may still be a possibility by switching the sign of the Q column
               elseif all((Mcell{1,jj}*(-1)*candidate(:,jj)-Mlcell{1,jj}).*(Mucell{1,jj}-Mcell{1,jj}*(-1)*candidate(:,jj))>=0)
               Q(:,jj)=-Q(:,jj);
               % else, if there is no way to have Q succesful, count it as a fail and switch the variable success to 0
               else
               success=0;
               end
            end
         jj=jj+1;
         end
      % finally, the only possible remaining case is that of pure zero restrictions, satisfied by construction: no need to check
      else
      end

   % now, if sucess is still equal to 1, it means that the draw was successful for all the restrictions: keep it
   % otherwise, if success has been switched to 0, there was at least one failure: discard the draw and try with a new draw
   end

   % store
   for jj=1:IRFperiods
   storage1{ii,1}(:,:,jj)=ortirfmatrix(:,:,jj)*Q;
   end
   storage2{ii,1}=hsigma*Q;

   hbar.iterate(1);   % update progress by one iteration

end

close(hbar);   %close progress bar

% reorganise storage
% loop over iterations
for ii=1:It-Bu
   % loop over IRF periods
   for jj=1:IRFperiods
      % loop over variables
      for kk=1:n
         % loop over shocks
         for ll=1:n
         struct_irf_record{kk,ll}(ii,jj)=storage1{ii,1}(kk,ll,jj);
         end
      end
   end
D_record(:,ii)=storage2{ii,1}(:);
gamma_record(:,ii)=bear.vec(eye(n));
end

fprintf('Accepted Draws in Percent of Total Number of Draws: %f', 100*(It-Bu)/(not_successful + It-Bu))


function [varargout]=subEdge(varargin)

%%

switch nargin
    case 2
        F=varargin{1};
        V=varargin{2};
        n=1;
        uniqueOpt=1;
    case 3
        F=varargin{1};
        V=varargin{2};
        n=varargin{3};
        uniqueOpt=1;
    case 4
        F=varargin{1};
        V=varargin{2};
        n=varargin{3};
        uniqueOpt=varargin{4};
end

%%

%Check if n can be achieved through splitting
nSplitIterations=log2(n+1); %Check for integer solution
logicInteger=abs(round(nSplitIterations)-nSplitIterations)<eps(nSplitIterations);

if uniqueOpt==1 && logicInteger
    subMethod='split';
else
    subMethod='seed';
end

switch subMethod
    case 'split' %iteratively split edges (no double points created, unique operation avoided)
        Fs=F; Vs=V;
        for q=1:1:nSplitIterations
            F=Fs; V=Vs;

            Fs=zeros(size(Fs,1),size(Fs,2)*2);
            Fs(:,1:2:end)=F;

            [E]=patchEdges(F,1);

            numPoints = size(V,1);
            numEdges = size(E,1);

            % Get indices of the edges associated with each face
            A = sparse(E(:,1),E(:,2),(1:numEdges)+numPoints,numPoints,numPoints,numEdges);
            A = max(A,A'); %Copy symmetric

            indFs=2:2:size(Fs,2);
            for qp=1:1:size(F,2)
                if qp<size(F,2)
                    indA_now=F(:,qp)+(F(:,qp+1)-1)*numPoints;
                else
                    indA_now=F(:,qp)+(F(:,1)-1)*numPoints;
                end

                indV_now=full(A(indA_now));
                Fs(:,indFs(qp))=indV_now;
            end

            %Create vertex array
            Vn=0.5*(V(E(:,1),:)+V(E(:,2),:)); %new mid-edge points
            Vs = [V; Vn]; %Join point sets
        end
    case 'seed' %Seed edge points and remove doubles (more memory intensive)
        %Edges matrix
        [E]=patchEdges(F,0);

        Vs=zeros(size(E,1)*(n+1),size(V,2));
        for q=1:1:size(V,2)
            X=V(:,q);
            XE=X(E);
            X_add=linspacen(XE(:,1),XE(:,2),n+2);
            X_add=X_add(:,1:end-1)';
            Vs(:,q)=X_add(:);
        end

        ind=(1:1:size(Vs,1))';
        Fs=reshape(ind,[(n+1)*size(F,2),size(Vs,1)./((n+1)*size(F,2))])';

        %Merge non-unique nodes
        [Fs,Vs]=mergeVertices(Fs,Vs);

end

%% Collect output
varargout{1}=Fs;
varargout{2}=Vs;

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/>.


function [bnet, LL, engine] = learn_params_dbn_em(engine, evidence, varargin)
% LEARN_PARAMS_DBN Set the parameters in a DBN to their ML/MAP values using batch EM.
% [bnet, LLtrace, engine] = learn_params_dbn_em(engine, data, ...)
%
% data{l}{i,t} = value of node i in slice t of time-series l, or [] if hidden.
%   Suppose you have L time series, each of length T, in an O*T*L array D,
%   where O is the num of observed scalar nodes, and N is the total num nodes per slice.
%   Then you can create data as follows, where onodes is the index of the observable nodes:
%      data = cell(1,L);
%      for l=1:L
%        data{l} = cell(N, T);
%        data{l}(onodes,:) = num2cell(D(:,:,l));
%      end
% Of course it is possible for different sets of nodes to be observed in
% each slice/ sequence, and for each sequence to be a different length.
%
% LLtrace is the learning curve: the vector of log-likelihood scores at each iteration.
%
% Optional arguments [default]
%
% max_iter - specifies the maximum number of iterations [100]
% thresh - specifies the thresold for stopping EM [1e-3]
%   We stop when |f(t) - f(t-1)| / avg < threshold,
%   where avg = (|f(t)| + |f(t-1)|)/2 and f is log lik.
% verbose - display loglik at each iteration [1]
% anneal - 1 means do deterministic annealing (only for entropic priors) [0]
% anneal_rate - geometric cooling rate [0.8]
% init_temp - initial annealing temperature [10]
% final_temp - final annealing temperature [1e-3]
%

max_iter = 100;
thresh = 1e-3;
anneal = 0;
anneal_rate = 0.8;
init_temp = 10;
final_temp = 1e-3;
verbose = 1;

for i=1:2:length(varargin)
  switch varargin{i}
   case 'max_iter', max_iter = varargin{i+1};
   case 'thresh', thresh = varargin{i+1};
   case 'anneal', anneal = varargin{i+1};
   case 'anneal_rate', anneal_rate = varargin{i+1};
   case 'init_temp', init_temp = varargin{i+1};
   case 'final_temp', final_temp = varargin{i+1};
   otherwise, error(['unrecognized argument' varargin{i}])
  end
end

% take 1 EM step at each temperature value, then when temp=0, run to convergence
% When using an entropic prior, Z = 1-T, so
% T=2 => Z=-1 (max entropy)
% T=1 => Z=0 (max likelihood)
% T=0 => Z=1 (min entropy / max structure)
num_iter = 1;
LL = [];
if anneal
  temperature = init_temp;
  while temperature > final_temp
    [engine, loglik, logpost] = EM_step(engine, evidence, temperature);
    if verbose
      fprintf('EM iteration %d, loglik = %8.4f, logpost = %8.4f, temp=%8.4f\n', ...
	      num_iter, loglik, logpost, temperature);
    end
    num_iter = num_iter + 1;
    LL = [LL loglik];
    temperature = temperature * anneal_rate;
  end
  temperature = 0;
  previous_loglik = loglik;
  previous_logpost = logpost;
else
  temperature = 0;
  previous_loglik = -inf;
  previous_logpost = -inf;
end

converged = 0;
while ~converged & (num_iter <= max_iter)
  [engine, loglik, logpost] = EM_step(engine, evidence, temperature);
  if verbose
    %fprintf('EM iteration %d, loglik = %8.4f, logpost = %8.4f\n', ...
    %	    num_iter, loglik, logpost);
    fprintf('EM iteration %d, loglik = %8.4f\n', num_iter, loglik);
  end
  num_iter = num_iter + 1;
  [converged, decreased] = em_converged(loglik, previous_loglik, thresh);
  %[converged, decreased] = em_converged(logpost, previous_logpost, thresh);
  previous_loglik = loglik;
  previous_logpost = logpost;
  LL = [LL loglik];
end

bnet = bnet_from_engine(engine);

%%%%%%%%%

function [engine, loglik, logpost] = EM_step(engine, cases, temp)

bnet = bnet_from_engine(engine); % engine contains the old params that are used for the E step
ss = length(bnet.intra);
CPDs = bnet.CPD; % these are the new params that get maximized
num_CPDs = length(CPDs);

% log P(theta|D) = (log P(D|theta) + log P(theta)) - log(P(D))
% where log P(D|theta) = sum_cases log P(case|theta)
% and log P(theta) = sum_CPDs log P(CPD) - only count once even if tied!
% logpost = log P(theta,D) (un-normalized)
% This should be negative, and increase at every step.

adjustable = zeros(1,num_CPDs);
logprior = zeros(1, num_CPDs);
for e=1:num_CPDs
  adjustable(e) = adjustable_CPD(CPDs{e});
end
adj = find(adjustable);

for e=adj(:)'
  logprior(e) = log_prior(CPDs{e});
  CPDs{e} = reset_ess(CPDs{e});
end

loglik = 0;
for l=1:length(cases)
  evidence = cases{l};
  if ~iscell(evidence)
    error('training data must be a cell array of cell arrays')
  end
  [engine, ll] = enter_evidence(engine, evidence);
  assert(~isnan(ll))
  loglik = loglik + ll;
  T = size(evidence, 2);

  % We unroll ns etc because in update_ess, we refer to nodes by their unrolled number
  % so that they extract evidence from the right place.
  % (The CPD should really store its own version of ns and cnodes...)
  ns = repmat(bnet.node_sizes_slice(:), [1 T]);
  cnodes = unroll_set(bnet.cnodes_slice, ss, T);

  %hidden_bitv = repmat(bnet.hidden_bitv(1:ss), [1 T]);
  hidden_bitv = zeros(ss, T);
  hidden_bitv(isemptycell(evidence))=1;
  % hidden_bitv(i) = 1 means node i is hidden.
  % We pass this in, rather than using isemptycell(evidence(dom)), because
  % isemptycell is very slow.

  t = 1;
  for i=1:ss
    e = bnet.equiv_class(i,1);
    if adjustable(e)
      fmarg = marginal_family(engine, i, t);
      CPDs{e} = update_ess(CPDs{e}, fmarg, evidence, ns(:), cnodes(:), hidden_bitv(:));
    end
  end

  for i=1:ss
    e = bnet.equiv_class(i,2);
    if adjustable(e)
      for t=2:T
	fmarg = marginal_family(engine, i, t);
	CPDs{e} = update_ess(CPDs{e}, fmarg, evidence, ns(:), cnodes(:), hidden_bitv(:));
      end
    end
  end
end

logpost = loglik + sum(logprior(:));

for e=adj(:)'
  CPDs{e} = maximize_params(CPDs{e}, temp);
end

engine = update_engine(engine, CPDs);


classdef (InferiorClasses = {?rotation,?quaternion}) orientation < rotation
%
% The class *orientation* represents orientations and misorientations.
%
% Syntax
%   ori = orientation(rot)
%   ori = orientation.byEuler(phi1,Phi,phi2,cs,ss)
%   ori = orientation.byEuler(alpha,beta,gamma,'ZYZ',cs,ss)
%   ori = orientation.byMiller([h k l],[u v w],cs,ss)
%   ori = orientation.byAxisAngle(v,omega,cs,ss)
%   ori = orientation.byMatrix(A,cs)
%   ori = orientation.map(u1,v1,u2,v2,cs)
%   ori = orientation.goss(cs)
%   ori = orientation.cube(cs)
%
% Input
%  rot    - @rotation
%  cs, ss - @crystalSymmetry / @specimenSymmetry
%
% Output
%  ori    - @orientation
%
% Class Properties
%  CS, SS          - @crystalSymmetry / @specimenSymmetry
%  antipodal       - grain exchange symmetry for misorientations
%  phi1, Phi, phi2 - Euler angles
%  i               - inversion
%  a, b, c, d      - quaternion components
%
% See also
% DefinitionAsCoordinateTransform CrystalOperations CrystalReferenceSystem

properties

  CS = crystalSymmetry('1');   % crystal symmetry
  SS = specimenSymmetry('1');  % specimen symmetry or crystal symmetry
  antipodal = false

end

methods

  function o = orientation(varargin)

    % find and remove symmetries
    args  = cellfun(@(s) isa(s,'symmetry'),varargin,'uniformoutput',true);
    sym = varargin(args);
    varargin(args) = [];

    % call rotation constructor
    o = o@rotation(varargin{:});

    if nargin == 0, return;end

    % set symmetry
    if ~isempty(varargin) && isa(varargin{1},'orientation')
      o.CS = varargin{1}.CS;
      o.SS = varargin{1}.SS;
      o.antipodal = varargin{1}.antipodal;
    elseif ~isempty(varargin) && ischar(varargin{1}) && strcmpi(varargin{1},'map')
      if isa(varargin{2},'Miller'), o.CS = varargin{2}.CS; end
      if isa(varargin{3},'Miller'), o.SS = varargin{3}.CS; end
    else
      try %#ok<TRYNC>
        a = get_option(varargin,'axis');
        o.CS = a.CS;
        o.SS = a.CS;
      end
    end
    if ~isempty(sym), o.CS = sym{1};end
    if length(sym) > 1, o.SS = sym{2};end

    % empty constructor -> done
    if isempty(varargin), return; end

    % copy constructor
    switch class(varargin{1})

      case 'char'

        switch lower(varargin{1})

          case 'miller'

            o = orientation.byMiller(varargin{:});

          otherwise

            if exist([varargin{1},'Orientation'],'file')

              % there is a file defining this specific orientation
              o = eval([varargin{1},'Orientation(o.CS,o.SS)']);

            end
        end
    end
    o.antipodal = o.antipodal | check_option(varargin,'antipodal');
    if o.antipodal && o.CS ~= o.SS
      warning('antipodal symmetry is only meaningfull for misorientations between the same phase.')
    end
  end
end

methods (Static = true)

  function ori = nan(varargin)
    s = varargin(cellfun(@isnumeric,varargin));
    q = quaternion.nan(s{:});
    ori = orientation(q,varargin{:});
  end

  function ori = id(varargin)
    id = find(~cellfun(@isnumeric,varargin),1)-1;
    q = quaternion.id(varargin{1:id});
    ori = orientation(q,varargin{id+1:end});
  end

  function ori = rand(varargin)
    q = quaternion.rand(varargin{:});
    ori = orientation(q,varargin{:});
  end

  ori = byMiller(m1,m2,varargin);
  ori = byEuler(phi1,Phi,phi2,varargin);
  ori = byAxisAngle(v,omega,varargin);
  ori = byMatrix(M,varargin);
  ori = map(varargin);
  ori = fit(varargin);
  [ori,interface,options] = load(fname,varargin);

  function ori = cube(varargin)
    ori = orientation.byEuler(0,0,0,varargin{:});
  end

  function ori = cubeND22(varargin)
    ori = orientation.byEuler(22*degree,0,0,varargin{:});
  end

  function ori = cubeND45(varargin)
    ori = orientation.byEuler(45*degree,0,0,varargin{:});
  end

  function ori = cubeRD(varargin)
    ori = orientation.byEuler(0,22*degree,0,varargin{:});
  end

  function ori = goss(varargin)
    ori = orientation.byEuler(0,45*degree,0,varargin{:});
  end

  function ori = copper(varargin)
    %ori = orientation.byEuler(90*degree,35*degree,45*degree,varargin{:});
    ori = orientation.byMiller([1 1 2],[-1 1 -1],varargin{:});
  end

  function ori = copper2(varargin)
    %ori = orientation.byEuler(270*degree,35*degree,45*degree,varargin{:});
    ori = orientation.byMiller([1 1 2],[1 -1 1],varargin{:});
  end

  function ori = SR(varargin)
    ori = orientation.byEuler(53*degree,35*degree,63*degree,varargin{:});
  end

  function ori = SR2(varargin)
    ori = orientation.byEuler(233*degree,35*degree,63*degree,varargin{:});
  end

  function ori = SR3(varargin)
    ori = orientation.byEuler(307*degree,35*degree,27*degree,varargin{:});
  end

  function ori = SR4(varargin)
    ori = orientation.byEuler(127*degree,35*degree,27*degree,varargin{:});
  end

  function ori = brass(varargin)
    ori = orientation.byEuler(35*degree,45*degree,0*degree,varargin{:});
  end

  function ori = brass2(varargin)
    ori = orientation.byEuler(325*degree,45*degree,0*degree,varargin{:});
  end

  function ori = PLage(varargin)
    ori = orientation.byEuler(65*degree,45*degree,0*degree,varargin{:});
  end

  function ori = PLage2(varargin)
    ori = orientation.byEuler(245*degree,45*degree,0*degree,varargin{:});
  end

  function ori = QLage(varargin)
    ori = orientation.byEuler(65*degree,20*degree,0*degree,varargin{:});
  end

  function ori = QLage2(varargin)
    ori = orientation.byEuler(245*degree,20*degree,0*degree,varargin{:});
  end

  function ori = QLage3(varargin)
    ori = orientation.byEuler(115*degree,160*degree,0*degree,varargin{:});
  end

  function ori = QLage4(varargin)
    ori = orientation.byEuler(295*degree,160*degree,0*degree,varargin{:});
  end

  function ori = invGoss(varargin)
    ori = orientation.byEuler(90*degree,45*degree,0*degree,varargin{:});
  end

  function mori = Bain(csGamma,csAlpha)
    %
    % Syntax:
    %   mori = Bain(csGamma,csAlpha)
    %
    % Input
    %  csGamma - parent @crystalSymmetry (cubic fcc)
    %  csAlpha - child @crystalSymmetry (cubic bcc)
    %

    mori = orientation.map(Miller(1,0,0,csGamma),Miller(1,0,0,csAlpha),...
      Miller(0,1,0,csGamma,'uvw'),Miller(0,1,1,csAlpha,'uvw'));
  end

  function mori = KurdjumovSachs(csGamma,csAlpha)
    %
    % Syntax:
    %   mori = KurdjumovSachs(csGamma,csAlpha)
    %
    % Input
    %  csGamma - parent @crystalSymmetry (cubic fcc)
    %  csAlpha - child @crystalSymmetry (cubic bcc)
    %

    mori = orientation.map(Miller(1,1,1,csGamma),Miller(0,1,1,csAlpha),...
      Miller(-1,0,1,csGamma,'uvw'),Miller(-1,-1,1,csAlpha,'uvw'));
  end

  function mori = NishiyamaWassermann(csGamma,csAlpha)
    %
    % Syntax:
    %   mori = NishiyamaWassermann(csGamma,csAlpha)
    %
    % Input
    %  csGamma - parent @crystalSymmetry (cubic fcc)
    %  csAlpha - child @crystalSymmetry (cubic bcc)
    %

    mori = orientation.map(Miller(1,1,1,csGamma),Miller(0,1,1,csAlpha),...
      Miller(1,1,-2,csGamma,'uvw'),Miller(0,-1,1,csAlpha,'uvw'));
  end

  function mori = Pitsch(csGamma,csAlpha)
    %
    % Syntax:
    %   mori = Pitch(csGamma,csAlpha)
    %
    % Input
    %  csGamma - parent @crystalSymmetry (cubic fcc)
    %  csAlpha - child @crystalSymmetry (cubic bcc)
    %

    mori = orientation.map(Miller(0,1,0,csGamma),Miller(1,0,1,csAlpha),...
      Miller(1,0,1,csGamma,'uvw'),Miller(-1,1,1,csAlpha,'uvw'));

    %mori = orientation.map(Miller(1,1,0,csGamma),Miller(1,1,1,csAlpha),...
    %  Miller(0,0,1,csGamma,'uvw'),Miller(-1,1,0,csAlpha,'uvw'));

  end

  function mori = GreningerTrojano(csGamma,csAlpha)
    %
    % Syntax:
    %   mori = orientation.GreningerTrojano(csGamma,csAlpha)
    %
    % Input
    %  csGamma - parent @crystalSymmetry (cubic fcc)
    %  csAlpha - child @crystalSymmetry (cubic bcc)
    %

    mori = inv(orientation.byEuler(2.7*degree,46.6*degree,7.5*degree,csAlpha,csGamma));

    %mori = orientation.map(Miller(1,1,1,csGamma),Miller(1,1,0,csAlpha),...
    %  Miller(5,12,17,csGamma,'uvw'),Miller(17,17,7,csAlpha,'uvw'));

  end

  function mori = Burgers(csParent,csChild)
    %
    % Syntax:
    %   mori = orientation.Burgers(csParent,csChild)
    %
    % Input
    %  csParent - parent @crystalSymmetry
    %  csChild  - child @crystalSymmetry
    %

    if csParent.lattice.isTriHex
      mori = inv(orientation.Burgers(csChild,csParent));
    else
      mori = orientation.map(Miller(1,1,0,csParent),Miller(0,0,0,1,csChild),...
        Miller(-1,1,-1,csParent),Miller(2,-1,-1,0,csChild));
    end

  end

  function mori = Burger(varargin)

    warning('orientation.Burger has been renamed to orientation.Burgers')

    mori = orientation.Burgers(varargin{:});

  end

  function mori = ShojiNishiyama(csGamma,csEps)
    %
    % Syntax:
    %   mori = orientation.ShojiNishiyama(csGamma,csEps)
    %
    % Input
    %  csGamma - parent @crystalSymmetry
    %  csEps  - child @crystalSymmetry
    %

    mori = orientation.map(Miller(1, 1, 1,csGamma),Miller(0, 0, 0, 1,csEps),...
      Miller(1, 0, -1,csGamma,'uvw'),Miller(2, -1, -1, 0,csEps,'uvw'));

  end

end

methods (Static = true, Hidden = true)

  function check_dot(cs)

    % first setting
    cs = crystalSymmetry('m-3m');
    n = 10000000;
    ori1 = orientation.rand(n,cs);
    ori2 = orientation.rand(n,cs);

    tic
    mori = inv(ori1) .* ori2;

    d1 = max(dot_outer(mori,cs.rot,'noSymmetry'),[],2);
    toc

    tic
    d2 = dot(ori1,ori2);
    toc

    norm(d1-d2)

    %second setting
    n = 1000000;
    ori1 = orientation.rand(n,cs,cs);
    ori2 = orientation.rand(cs,cs);

    tic
    d1 = dot(ori1,ori2);
    toc

    tic
    d2 = dot(ori2,ori1);
    toc

    norm(d1-d2)

  end

end

end


function [Eft, Varft, lpyt, Eyt, Varyt] = gpsvi_predgrad(gp,x,y,varargin)
%GPSVI_PREDGRAD  Make predictions with SVI GP
%
%  Description
%    [EFT, VARFT] = GPSVI_PREDGRAD(GP, X, Y, XT, OPTIONS)
%    takes a GP structure together with matrix X of training inputs and
%    vector Y of training targets, and evaluates the predictive
%    distribution at test inputs XT. Returns a posterior mean EFT and
%    variance VARFT of latent variables. Each row of X corresponds to one
%    input vector and each row of Y corresponds to one output vector.
%
%    [EFT, VARFT, LPYT] = GPSVI_PREDGRAD(GP, X, Y, XT, 'yt', YT, OPTIONS)
%    returns also logarithm of the predictive density LPYT of the
%    observations YT at test input locations XT. This can be used
%    for example in the cross-validation. Here Y has to be a vector.
%
%    [EFT, VARFT, LPYT, EYT, VARYT] = GPSVI_PREDGRAD(GP, X, Y, XT, OPTIONS)
%    returns also the posterior predictive mean EYT and variance VARYT.
%
%    [EF, VARF, LPY, EY, VARY] = GPSVI_PREDGRAD(GP, X, Y, OPTIONS)
%    evaluates the predictive distribution at training inputs X
%    and logarithm of the predictive density LPY of the training
%    observations Y.
%
%    OPTIONS is optional parameter-value pair
%      predcf - an index vector telling which covariance functions are
%               used for prediction. Default is all (1:gpcfn).
%               See additional information below.
%      yt     - optional observed yt in test points (see below)
%      z      - optional observed quantity in triplet (x_i,y_i,z_i)
%               Some likelihoods may use this. For example, in case of
%               Poisson likelihood we have z_i=E_i, that is, expected value
%               for ith case.
%      zt     - optional observed quantity in triplet (xt_i,yt_i,zt_i)
%               Some likelihoods may use this. For example, in case of
%               Poisson likelihood we have z_i=E_i, that is, the expected
%               value for the ith case.
%
%  See also
%    GPSVI_PRED, SVIGP, DEMO_SVI*
%

% Copyright (c) 2014 Ville Tolvanen

% This software is distributed under the GNU General Public
% License (version 3 or later); please refer to the file
% License.txt, included with the software, for details.

ip=inputParser;
ip.FunctionName = 'GP_PRED';
ip.addRequired('gp',@(x) isstruct(x) || iscell(x));
ip.addRequired('x', @(x) ~isempty(x) && isreal(x) && all(isfinite(x(:))))
ip.addRequired('y', @(x) ~isempty(x) && isreal(x) && all(isfinite(x(:))))
ip.addOptional('xt', [], @(x) isempty(x) || (isreal(x) && all(isfinite(x(:)))))
ip.addParamValue('yt', [], @(x) isreal(x) && all(isfinite(x(:))))
ip.addParamValue('zt', [], @(x) isreal(x) && all(isfinite(x(:))))
ip.addParamValue('z', [], @(x) isreal(x) && all(isfinite(x(:))))
ip.addParamValue('predcf', [], @(x) isempty(x) || ...
                 isvector(x) && isreal(x) && all(isfinite(x)&x>=0))
if numel(varargin)==0 || isnumeric(varargin{1})
  % inputParser should handle this, but it doesn't
  ip.parse(gp, x, y, varargin{:});
else
  ip.parse(gp, x, y, [], varargin{:});
end
xt=ip.Results.xt;
yt=ip.Results.yt;
zt=ip.Results.zt;
z=ip.Results.z;
predcf=ip.Results.predcf;
if isempty(xt)
  xt=x;
  if isempty(yt)
    yt=y;
  end
  if isempty(zt)
    zt=z;
  end
end

tn = size(x,1);
if nargout > 2 && isempty(yt)
  lpyt=[];
end

%     [tmp,tmp,tmp,param]=gpsvi_e(gp_pak(gp),gp,x,y);

% Check if the variational parameters has been set
if ~isfield(gp, 'm') || ~isfield(gp, 'S')
  error('Variational parameters has not been set. Call SVIGP first.')
end

u = gp.X_u;
m=gp.m;
S=gp.S;
if size(u,2) ~= size(x,2)
  % Turn the inducing vector on right direction
  u=u';
end
% Calculate some help matrices
K_uu = gp_trcov(gp, u);     % u x u, noiseles covariance K_uu
K_nu = gp_dcov2(gp, u, [], xt, xt);
K_nu = (K_nu(:,size(xt,1)+1:end))';
%     K_nu = gp_cov(gp,xt,u);       % n x u
[Luu, notpositivedefinite] = chol(K_uu,'lower');
if notpositivedefinite
  Eft=NaN; Varft=NaN; lpyt=NaN; Eyt=NaN; Varyt=NaN;
  return
end

Eft = K_nu*(Luu'\(Luu\m));

if nargout > 1
  [Knn_v, Cnn_v] = gp_trvar(gp,xt,predcf);
  B2=Luu\(K_nu');
  B3=K_uu\(K_nu');

  Varft = Knn_v - sum(B2.*B2)' + sum(B3.*(S*B3))';
end
s2=gp.lik.sigma2;

if nargout > 2
  if isequal(gp.lik.type, 'Gaussian')
    Eyt = Eft;
    Varyt = Varft + Cnn_v - Knn_v;
    if ~isempty(yt)
      lpyt = norm_lpdf(yt, Eyt, sqrt(Varyt));
    end
  else
    if nargout>3
      [lpyt, Eyt, Varyt] = gp.lik.fh.predy(gp.lik, Eft, Varft+s2, yt, zt);
    else
      lpyt = gp.lik.fh.predy(gp.lik, Eft, Varft+s2, yt, zt);
    end
  end
end
end

function [C, Cinv] = gp_dcov2(gp, x1, xv1, x2, xv2, predcf)
% Replacement for GP_DCOV where virtual inputs are given as arguments.

% Split the training data for normal latent input and gradient inputs
x12=x1;
%x11=gp.xv;
x11=xv1;
x3=xv2;

% Derivative observations
[n,m]=size(x1);
[n4,m4]=size(x2);
ncf=length(gp.cf);
if isfield(gp, 'nvd')
  % Only specific dimensions
  ii1=abs(gp.nvd);
else
  % All dimensions
  ii1=1:m;
end
for i1=1:ncf
  gpcf = gp.cf{i1};
  if m==1
    Gset2 = gpcf.fh.ginput4(gpcf, x3, x12);
    Kff = gpcf.fh.cov(gpcf, x12, x2);
    if ~isempty(x11)
      Gset1 = gpcf.fh.ginput4(gpcf, x11, x2);
      Kdd = gpcf.fh.ginput2(gpcf, x11, x3);
      Kdf=Gset1{1};
    else
      Kdd={[]};
      Kdf=[];
    end

    Kfd=Gset2{1};
    C = [Kff Kfd'; Kdf Kdd{1}];

    % Input dimension is >1
  else
    [n,m]=size(x11);
    [n2,m2]=size(x3);

    Kff = gpcf.fh.cov(gpcf, x12, x2);
    Gset2 = gpcf.fh.ginput4(gpcf, x3, x12);

    %Gather matrices from Gset (d k(x1,x2) /d x1)
    Kfd22=cat(2,Gset2{ii1});
    Kdf22=cat(1,Gset2{ii1})';

    if ~isempty(x11)
      Gset1 = gpcf.fh.ginput4(gpcf, x11,x2);
      Kfd=cat(2,Gset1{ii1});
      Kdf=cat(1,Gset1{ii1});
      % both x derivatives, same dimension (to diagonal blocks)
      D = gpcf.fh.ginput2(gpcf, x11, x3);
      % both x derivatives, different dimension (non-diagonal blocks)
      Kdf2 = gpcf.fh.ginput3(gpcf, x11 ,x3);

      % Now build up Kdd m*n x m*n2 matrix, which contains all the
      % both partial derivative" -matrices

      % Add the diagonal matrices
      Kdd=blkdiag(D{1:m});
      % Add the non-diagonal matrices to Kdd
      ii3=0;
      for j=0:m-2
        for i=1+j:m-1
          ii3=ii3+1;
          Kdd(i*n+1:(i+1)*n,j*n2+1:j*n2+n2) = Kdf2{ii3};
          Kdd(j*n+1:j*n+n,i*n2+1:(i+1)*n2) = Kdf2{ii3};
        end
      end
      if isfield(gp, 'nvd')
        % Collect the correct gradient dimensions, i.e. select the blocks
        % that correspond to the input dimensions for which we want the
        % gradients to be monotonic
        Kddtmp=[];
        for ii2=1:length(ii1)
          for ii3=ii2:length(ii1)
            Kddtmp((ii2-1)*n+1:ii2*n, (ii3-1)*n2+1:ii3*n2) = ...
              Kdd((ii1(ii2)-1)*n+1:ii1(ii2)*n,(ii1(ii3)-1)*n2+1:ii1(ii3)*n2);
            if ii2~=ii3
              Kddtmp((ii3-1)*n+1:ii3*n, (ii2-1)*n2+1:ii2*n2) = ...
                Kdd((ii1(ii3)-1)*n+1:ii1(ii3)*n,(ii1(ii2)-1)*n2+1:ii1(ii2)*n2);
            end
          end
        end
        Kdd=Kddtmp;
      end
    else
      Kdf=[];
      Kdd=[];
    end

    % Gather all the matrices into one final matrix K which is the
    % training covariance matrix
    C = [Kff Kdf22; Kdf Kdd];
    %   C = [Kff; Kdf];
  end
  if i1==1
    CC=C;
  else
    CC=CC+C;
  end
end
C=CC;
end


function S_scalar2d = poynting(varargin)

chkarg(nargin == 5 || nargin == 7, 'five or seven arguments are required.')

iarg = 0;
iarg = iarg + 1; polarization = varargin{iarg};
chkarg(istypesizeof(polarization, 'Axis'), '"argument %d should be "polarization" (instance of Axis).', iarg);

if istypesizeof(varargin{iarg+1}, 'Scalar3d')
	iarg = iarg + 1; Ep3d = varargin{iarg};
	chkarg(istypesizeof(Ep3d, 'Scalar3d'), '"argument %d should be "Ep3d" (instance of Scalar3d)."', iarg);

	iarg = iarg + 1; Eq3d = varargin{iarg};
	chkarg(istypesizeof(Eq3d, 'Scalar3d'), '"argument %d should be "Eq3d" (instance of Scalar3d)."', iarg);

	iarg = iarg + 1; Hp3d = varargin{iarg};
	chkarg(istypesizeof(Hp3d, 'Scalar3d'), '"argument %d should be "Hp3d" (instance of Scalar3d)."', iarg);

	iarg = iarg + 1; Hq3d = varargin{iarg};
	chkarg(istypesizeof(Hq3d, 'Scalar3d'), '"argument %d should be "Hq3d" (instance of Scalar3d)."', iarg);

	iarg = iarg + 1; normal_axis = varargin{iarg};
	chkarg(istypesizeof(normal_axis, 'Axis'), 'argument %d should be "normal_axis" (instance of Axis).', iarg);

	iarg = iarg + 1; intercept = varargin{iarg};
	chkarg(istypesizeof(intercept, 'real'), 'argument %d should be "intercept" (real).', iarg);

	chkarg(isequal(Ep3d.grid3d, Eq3d.grid3d, Hp3d.grid3d, Hq3d.grid3d), ...
		'instances of Scalar3d do not have same grid3d.');

	% This makes the Poynting vector calculation independnet of whether the
	% primary grid is the E-field grid or H-field grid, when normal_axis is
	% normal to p and q.
	Ep2d = slice_scalar3d(Ep3d, normal_axis, intercept);
	Eq2d = slice_scalar3d(Eq3d, normal_axis, intercept);
	Hp2d = slice_scalar3d(Hp3d, normal_axis, intercept);
	Hq2d = slice_scalar3d(Hq3d, normal_axis, intercept);

	grid2d = Ep2d.grid2d;
else
	iarg = iarg + 1; Ep2d = varargin{iarg};
	chkarg(istypesizeof(Ep2d, 'Scalar2d'), '"argument %d should be "Ep2d" (instance of Scalar2d)."', iarg);

	iarg = iarg + 1; Eq2d = varargin{iarg};
	chkarg(istypesizeof(Eq2d, 'Scalar2d'), '"argument %d should be "Eq2d" (instance of Scalar2d)."', iarg);

	iarg = iarg + 1; Hp2d = varargin{iarg};
	chkarg(istypesizeof(Hp2d, 'Scalar2d'), '"argument %d should be "Hp2d" (instance of Scalar2d)."', iarg);

	iarg = iarg + 1; Hq2d = varargin{iarg};
	chkarg(istypesizeof(Hq2d, 'Scalar2d'), '"argument %d should be "Hq2d" (instance of Scalar2d)."', iarg);

	chkarg(isequal(Ep2d.grid2d, Eq2d.grid2d, Hp2d.grid2d, Hq2d.grid2d), ...
		'instances of Scalar2d do not have same grid2d.');  % Grid2d has normal_axis, so passing this test implies shared normal axis
	chkarg(isequal(Ep2d.intercept, Eq2d.intercept, Hp2d.intercept, Hq2d.intercept), ...
		'instances of Scalar2d do not have same intercept.');

	grid2d = Ep2d.grid2d;
	normal_axis = grid2d.normal_axis;
	intercept = Ep2d.intercept;
end

if polarization == normal_axis
	assert(all(Ep2d.gt_array==Hq2d.gt_array) && all(Eq2d.gt_array==Hp2d.gt_array));

	pi = grid2d.l{Dir.h,GT.dual};
	qi = grid2d.l{Dir.v,GT.dual};
	[PI, QI] = ndgrid(pi, qi);  % centers of grid cell faces

	[ep, l1] = Ep2d.data_expanded();
	hq = Hq2d.data_expanded();  % hq and ep have same location
	[eq, l2] = Eq2d.data_expanded();
	hp = Hp2d.data_expanded();  % hp and eq have same location

	sr1 = ep .* conj(hq);
	sr2 = eq .* conj(hp);

	[P1, Q1] = ndgrid(l1{:});  % locations of sr1
	[P2, Q2] = ndgrid(l2{:});  % locations of sr2

	% Interpolate sr1 and sr2 at the centers of grid cell faces.
	sr1 = interpn(P1, Q1, sr1, PI, QI);
	sr2 = interpn(P2, Q2, sr2, PI, QI);

	array = real(sr1 - sr2) / 2;

	% Attach extra points.
	for d = Dir.elems
		array = attach_extra_S(array, d, grid2d);
	end

	osc = Ep2d.osc;
	physQ = PhysQ.S;
	gt_array = [GT.dual, GT.dual];  % face centers
else  % polarization ~= normal_axis
	% To be implemented.  See an attempted usage in example/2d/pc_2d_basic.m.
	% Note that in this case, Ep and Hq are not co-located.  Neither are Eq and
	% Hp.  (Draw their locations, and compare it with Ew2d.gt_array and
	% Hw2d.gt_array.)
end

% The attached values to array should be the same as the ones inside the array
% if BC is not periodic.
S_scalar2d = Scalar2d(array, grid2d, gt_array, osc, physQ, ['<', physQ.symbol, '_', char(polarization), '>'], intercept);

function array = attach_extra_S(array, d, grid2d)
ind_n = {':', ':'};
ind_p = {':', ':'};
bc_d = grid2d.bc(d);
if bc_d == BC.p
	ind_n{d} = grid2d.N(d);
	ind_p{d} = 1;
else  % bc_d == BC.e or BC.m
	ind_n{d} = 1;
	ind_p{d} = grid2d.N(d);
end
array = cat(int(d), array(ind_n{:}), array, array(ind_p{:}));  % Bloch phases in S are ignored due to conj(H)


function slc = resampleScanSlice(scanSetF, scanSetM)
%
% Copyright 2010, Joseph O. Deasy, on behalf of the CERR development team.
%
% This file is part of The Computational Environment for Radiotherapy Research (CERR).
%
% CERR development has been led by:  Aditya Apte, Divya Khullar, James Alaly, and Joseph O. Deasy.
%
% CERR has been financially supported by the US National Institutes of Health under multiple grants.
%
% CERR is distributed under the terms of the Lesser GNU Public License.
%
%     This version of CERR 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.
%
% CERR 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 CERR.  If not, see <http://www.gnu.org/licenses/>.

global planC stateS;
indexS = planC{end};

    [xV1, yV1, zV1] = getScanXYZVals(planC{indexS.scan}(scanSetF));
    [xV2, yV2, zV2] = getScanXYZVals(planC{indexS.scan}(scanSetM));

    XYZRes1  = {length(xV1), length(yV1), length(zV1)};

    xLims1 = [min(xV1) max(xV1)];
    yLims1 = [max(yV1) min(yV1)];
    zLims1 = [min(zV1) max(zV1)];

    sliceXVals1 = linspace(xLims1(1), xLims1(2), XYZRes1{1});
    sliceYVals1 = linspace(yLims1(1), yLims1(2), XYZRes1{2});
    sliceZVals1 = linspace(zLims1(1), zLims1(2), XYZRes1{3});

    for i=1:length(sliceZVals1)
        [xM1, yM1, zM1] = meshgrid(sliceXVals1, sliceYVals1, sliceZVals1(i));

        %Apply transformation to the limits if necessary.
        if isfield(planC{indexS.scan}(scanSetM), 'transM')
            mat = [xM1(:) yM1(:) zM1(:) ones(prod(size(xM1)), 1)]';
            mat = inv(planC{indexS.scan}(scanSetM).transM) * mat;
            xM = mat(1,:);
            yM = mat(2,:);
            zM = mat(3,:);
        end

        %Find corners of scan data included in this slice.
        [minX, jnk] = findnearest(xV2, min(xM(:)));
        [jnk, maxX] = findnearest(xV2, max(xM(:)));
        [minY, jnk] = findnearest(yV2, min(yM(:)));
        [jnk, maxY] = findnearest(yV2, max(yM(:)));

        [minZ, jnk] = findnearest(zV2, min(zM(:)));
        [jnk, maxZ] = findnearest(zV2, max(zM(:)));

        %Prepare the x,y,z vector inputs for finterp3.
        xVec = [xV2(1) xV2(2)-xV2(1) xV2(end)];
        yVec = [yV2(1) yV2(2)-yV2(1) yV2(end)];
        zVec = zV2(minZ:maxZ);

        %Interpolate to get slice.
        im = finterp3(xM(:), yM(:), zM(:), planC{indexS.scan}(scanSetM).scanArray(maxY:minY, minX:maxX, minZ:maxZ), xVec, yVec, zVec,0);

        imgSize      = [length(sliceYVals1) length(sliceXVals1) 1];
        im = reshape(im, imgSize);
        im = squeeze(im);
        slc(:,:,i) = im;
    end

end


% Defining A Sensor Mask By Opposing Corners Example
%
% This example demonstrates how to define a sensor mask using the grid
% coordinates of two opposing corners of a rectangle, and then use this for
% the detection of the pressure field generated by an initial pressure
% distribution within a two-dimensional homogeneous propagation medium. It
% builds on the Homogeneous Propagation Medium and Using A Binary Sensor
% Mask examples.
%
% author: Bradley Treeby
% date: 21st August 2014
% last update: 21st August 2014
%
% This function is part of the k-Wave Toolbox (http://www.k-wave.org)
% Copyright (C) 2009-2014 Bradley Treeby and Ben Cox

% This file is part of k-Wave. k-Wave is free software: you can
% redistribute it and/or modify it under the terms of the GNU Lesser
% General Public License as published by the Free Software Foundation,
% either version 3 of the License, or (at your option) any later version.
%
% k-Wave 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 Lesser General Public License for
% more details.
%
% You should have received a copy of the GNU Lesser General Public License
% along with k-Wave. If not, see <http://www.gnu.org/licenses/>.

clear all;

% =========================================================================
% SIMULATION
% =========================================================================

% create the computational grid
Nx = 128;           % number of grid points in the x (row) direction
Ny = 128;           % number of grid points in the y (column) direction
dx = 0.1e-3;        % grid point spacing in the x direction [m]
dy = 0.1e-3;        % grid point spacing in the y direction [m]
kgrid = makeGrid(Nx, dx, Ny, dy);

% define the properties of the propagation medium
medium.sound_speed = 1500; 	% [m/s]
medium.alpha_coeff = 0.75;  % [dB/(MHz^y cm)]
medium.alpha_power = 1.5;

% create initial pressure distribution using makeDisc
disc_magnitude = 5; % [Pa]
disc_x_pos = 50;    % [grid points]
disc_y_pos = 50;    % [grid points]
disc_radius = 8;    % [grid points]
disc_1 = disc_magnitude*makeDisc(Nx, Ny, disc_x_pos, disc_y_pos, disc_radius);

disc_magnitude = 3; % [Pa]
disc_x_pos = 80;    % [grid points]
disc_y_pos = 60;    % [grid points]
disc_radius = 5;    % [grid points]
disc_2 = disc_magnitude*makeDisc(Nx, Ny, disc_x_pos, disc_y_pos, disc_radius);

source.p0 = disc_1 + disc_2;

% define the first rectangular sensor region by specifying the location of
% opposing corners
rect1_x_start = 25;
rect1_y_start = 31;
rect1_x_end   = 30;
rect1_y_end   = 50;

% define the second rectangular sensor region by specifying the location of
% opposing corners
rect2_x_start = 71;
rect2_y_start = 81;
rect2_x_end   = 80;
rect2_y_end   = 90;

% assign the list of opposing corners to the sensor mask
sensor.mask = [rect1_x_start, rect1_y_start, rect1_x_end, rect1_y_end;...
               rect2_x_start, rect2_y_start, rect2_x_end, rect2_y_end].';

% run the simulation
sensor_data = kspaceFirstOrder2D(kgrid, medium, source, sensor);

% query the size of the output data - note that when using a sensor mask
% defined using opposing corners, the output data is returned as a
% structure
size(sensor_data(1).p)
size(sensor_data(2).p)

% =========================================================================
% VISUALISATION
% =========================================================================

% plot a time trace from the centre of each rectangle
figure;
plot(squeeze(sensor_data(1).p(end/2, end/2, :)), 'r-');
hold on;
plot(squeeze(sensor_data(2).p(end/2, end/2, :)), 'b-');
legend('Trace from centre of first rectangle', 'Trace from centre of second rectangle');
xlabel('Time Index');
ylabel('Pressure');
axis tight;


function [GraphObj]=createMetIntrcNetwork(model,metAbbr,varargin)
% Create directed metabolite-metabolite interaction network using given metabolites and the model.
% The produced network consists of given metabolites and its first neighbours.
% Colour and width of edges will be adjusted based on flux values If fluxes of the model are given.
% Another metabolite-metabolite interaction network will be generated if the node is clicked;
%
% Left click :Generate sub-metabolite-metabolite interaction network from the created figure.
%            This functionality was added for better looking at the created network
%            and showing flux values on edges lines.
%
% Right click :Generate metabolite-metabolite interaction network from model. This
%             functionality was added for creating metabolite-metabolite network using
%             clicked metabolite and model.
% The produced figure by right and left click on the main figure also
% has same property with the main figure, and it is clickable too.
%
% Note : If the fluxes are not given, irreversible COBRA model structure should use for
%        representing reversible reactions. See convertToIrreversible function at github page of cobratoolbox
%        https://opencobra.github.io/cobratoolbox/stable/modules/reconstruction/refinement/
% USAGE:
%
%    [GraphObj]=createMetIntrcNetwork(model,metAbbr,varargin)
%
% INPUTS:
%
%    model:             COBRA model structure
%    metAbbr:           List of metabolite abbreviation as a cell array
%
% OPTIONAL INPUT:
%    varargin:      Optional Inputs provided as 'ParameterName', Value
%                   pairs. the following parameternames are available:
%
%                   * fluxes:             flux vector
%                   * Graphtitle:         Title for a figure as a string
%                   * excludedMets:       As a cell array, Metabolite abbreviations that desired for exclude
%                   * nodeColour:         Colour for nodes (RGB Triplet) e.g. [0 1 0]
%                                         see https://www.mathworks.com/help/matlab/ref/colorspec.html
%                   * Hnodecolour:         Colour for nodes that will be highlighted e.g. [0 1 1]
%                   * scaleMin            Minimum value for scaling colorbar
%                   * scaleMax            Maximum value for scaling colorbar
%                   * nodeSize            Size of Nodes
%                   * HnodeSize           Highlighted node size
%                   * arrowSize           Arrow Size
%                   * threshold           Treshold for edges, the edges that has the flux value
%                                         below the treshold will be deleted, and if the node don't
%                                         have any relationship with any other nodes will be deleted.
%                   * excNodesWithDeg     It deletes nodes that has not degree in a given range.
%                                         It should be given as [minimum degree maximum degree].
%                                         Example, [1 50]. it deletes nodes that has lower than 1 degree
%                                         and higher than 50 degree.
%
% OUTPUT:
%    GraphObj:              Matlab digraph object
%
%
% EXAMPLES:
%    1) create network with succ[c] and akg[c]
%       [GraphObj]=createMetIntrcNetwork(model,{'succ[c]','akg[c]'})
%    2) create network with flux values
%       [GraphObj]=createMetIntrcNetwork(model,{'succ[c]','akg[c]'},'fluxes',fluxvector)
%    3) create network with flux values, and exculude exclude very employed metabolites
%       [GraphObj]=createMetIntrcNetwork(model,{'succ[c]','akg[c]'},'fluxes',fluxvector,'excludedMets',{'atp[c]','adp[c]'})
%    4) create network with scaling colorbar, [0 1]
%       [GraphObj]=createMetIntrcNetwork(model,{'succ[c]','akg[c]'},'fluxes',fluxvector,'excludedMets',{'atp[c]','adp[c]'},'scaleMax',1,'scaleMin',0)
%
% .. Author: - Kadir KOCABAS November/2020

defaultGraphtitle ='Metabolite-Metabolite Interaction Network';
defaultexcludedMets ={};
defaultFluxes=ones(size(model.rxns,1),1);
defaultnodeColour=[0.9 0.7 1];
defaultHnodecolour=[0 1 0];
defaultscaleMax=1e-6;
defaultscaleMin=0;
defaultNodeSize=15;
defaultHNodeSize=25;
defaultarrowSize=9;
defaultThreshold=1e-7;
defaultexcNodesWithDeg=[0 1e+6];

p = inputParser;
addRequired(p,'model',@isstruct);
addRequired(p,'metAbbr',@iscell);
addParameter(p,'fluxes',defaultFluxes,@isvector);
addParameter(p,'Graphtitle',defaultGraphtitle,@(x) isstring(x) || ischar(x));
addParameter(p,'excludedMets',defaultexcludedMets,@iscell);
addParameter(p,'nodeColour',defaultnodeColour,@isvector);
addParameter(p,'Hnodecolour',defaultHnodecolour,@isvector);
addParameter(p,'nodeSize',defaultNodeSize,@isnumeric);
addParameter(p,'HnodeSize',defaultHNodeSize,@isnumeric);
addParameter(p,'arrowSize',defaultarrowSize,@isnumeric);
addParameter(p,'scaleMax',defaultscaleMax,@isnumeric);
addParameter(p,'scaleMin',defaultscaleMin,@isnumeric);
addParameter(p,'threshold',defaultThreshold,@isnumeric);
addParameter(p,'excNodesWithDeg',defaultexcNodesWithDeg,@isvector);

parse(p,model,metAbbr,varargin{:});
p.KeepUnmatched = true;
model=p.Results.model;
metAbbr=p.Results.metAbbr;
fluxes=p.Results.fluxes;
Graphtitle=p.Results.Graphtitle;
excludedMets=p.Results.excludedMets;
nodeColour=p.Results.nodeColour;
Hnodecolour=p.Results.Hnodecolour;
scaleMin=p.Results.scaleMin;
scaleMax=p.Results.scaleMax;
nodeSize=p.Results.nodeSize;
HnodeSize=p.Results.HnodeSize;
arrowSize=p.Results.arrowSize;
threshold=p.Results.threshold;
excNodesWithDeg=p.Results.excNodesWithDeg;

%Controlling scale values
if ~(scaleMax>scaleMin)
    error('scaleMax should bigger than scaleMin');
end

%controlling degre values
if (excNodesWithDeg(1)>excNodesWithDeg(2))
    error('maximum degree should bigger or equal to minimum degree');
end

%controlling if flux vakues given
if (sum(fluxes)==size(model.rxns,1)&all(ismember(fluxes,1)))
    fcont=1;

else
    fcont=0;
end

if ((fcont==1) & ~(scaleMax==1e-6 && scaleMin==0) )
    warning('Scales are given without giving fluxes')
end

if ~all(ismember(metAbbr,model.mets))
    str = strjoin( metAbbr(~ismember(metAbbr,model.mets)), ' ' );
    warning(['These metabolites are not found in model : ' str])
end

%if user wants to delete edges with 0 fluxes treeshold assign 1e-5. Because
%weight of edges with zero fluxes shown as 1e-6 since weight of edges cannot
%be 0 in the matlab graph objects

if threshold==0
    threshold=1e-5;
end

%Create table, creating table without fluxrates if fluxes are not supplied
IndexesofMets=find(ismember(model.mets,metAbbr));
metMatrix=~ismember(model.S(IndexesofMets,:),0);
Rxns=model.rxns(any(metMatrix,1));
FluxRes=fluxes(ismember(model.rxns,Rxns));
%
%Get sub S matrix from S matrix
RxnsMatrx=~ismember(model.S(:,any(metMatrix,1)),0);
InvolvedMets=model.mets(any(RxnsMatrx,2));
adjunc_mat=model.S(any(RxnsMatrx,2),any(metMatrix,1));
%
%Multiply sub S matrix with flux values to keep flux value information
FluxMatrix  =FluxRes+1e-6;
FluxMatrix = repmat(FluxMatrix',size(adjunc_mat,1),1);
adjunc_mat=adjunc_mat.*FluxMatrix;

%Create left and right matrix( left matrix : comsumed metabolites, righ matrix:
%produced metabolites)
leftMatrix=adjunc_mat;
leftMatrix(leftMatrix > 0)=0;
rightMatrix=adjunc_mat;
rightMatrix(rightMatrix < 0)=0;
rightMatrix(rightMatrix > 0)=1;

%create adjacency matrix
A=(leftMatrix*rightMatrix')*(-1);

%Delete metabolites that is not first neighbour of metAbbr
idx=ismember(InvolvedMets,metAbbr);
newadjMtrx=zeros(size(A));
newadjMtrx(idx,:)=A(idx,:);
newadjMtrx(:,idx)=A(:,idx);
rows=any(newadjMtrx,1);
cols=any(newadjMtrx,2);
indofMets=rows'|cols;
metnames=InvolvedMets(indofMets);
newadjMtrx=newadjMtrx(indofMets,indofMets);
A=newadjMtrx;

%create graph object.
G = digraph(A,metnames);

GraphObj=G;
excludedMets=excludedMets(~ismember(excludedMets,metAbbr));

%Exclude given metabolites
G=rmnode(G,excludedMets);

if threshold~=1e-7
    edesBelowTresholdIdx=find(G.Edges.Weight<threshold);
    G=rmedge(G,edesBelowTresholdIdx);
end

%controlling if there is a node with given range
nodesIndegree=indegree(G);
nodesoutdegree=outdegree(G);
totalDegree=nodesIndegree+nodesoutdegree;
minDegree=excNodesWithDeg(1);
maxDegree=excNodesWithDeg(2);

if (max(totalDegree) < minDegree | min(totalDegree) > maxDegree)
    error('There is no node with degree in the given range')
end

G=rmnode(G,find(totalDegree<minDegree | totalDegree>maxDegree ));

figure;
hold on

%chech if there is a edge in the network
if ~isempty(G.Edges.Weight)
    G.Edges.LWidths = (G.Edges.Weight-min(G.Edges.Weight))/(max(G.Edges.Weight)-min(G.Edges.Weight))+0.00001;
    edgeCont=true;
else
    warning('There is no edges in the Network');
    edgeCont=false;
end

%create plot
h=plot(G,'MarkerSize',nodeSize,'NodeColor',nodeColour,'ArrowSize',arrowSize,'NodeLabelMode','auto','Layout','force','UseGravity',true);
metAbbr=metAbbr(ismember(metAbbr,G.Nodes.Name));
highlight(h,metAbbr,'NodeColor',Hnodecolour,'MarkerSize',HnodeSize);

nl = h.NodeLabel;
h.NodeLabel = '';
xd = get(h, 'XData');
yd = get(h, 'YData');
title(Graphtitle,'Interpreter', 'none');
txt=text(xd, yd, nl, 'FontSize',8, 'FontWeight','bold', 'HorizontalAlignment','center', 'VerticalAlignment','middle');
set(txt,'Interpreter', 'none');
%Define function for texts
set(txt,'ButtonDownFcn',{@hit_node,A,metnames,model,fluxes,excludedMets,Graphtitle,nodeColour,Hnodecolour,fcont,scaleMin,scaleMax,nodeSize,HnodeSize,arrowSize,threshold,excNodesWithDeg});

%define color and width of edges if fluxes are given
if edgeCont % control if there is edge in the network
    if  fcont==1
        h.LineWidth=1;
    elseif ~any(isnan(G.Edges.LWidths))
        h.LineWidth=G.Edges.LWidths*2.5;
        colormap jet(10)
        h.EdgeCData=G.Edges.Weight;
        hcb=colorbar;
        colorTitleHandle = get(hcb,'Title');
    else
        h.LineWidth=1;
        colormap jet(10)
        h.EdgeCData=G.Edges.Weight;
        hcb=colorbar;
        colorTitleHandle = get(hcb,'Title');

    end

    if fcont==0
        if ~(scaleMax==1e-6 && scaleMin==0)
            caxis([scaleMin scaleMax])
            titleBar = ['Scaled Fluxes',' ', '(', num2str(scaleMin),' - ',num2str(scaleMax),')'];
            set(colorTitleHandle ,'String',titleBar,'FontWeight','Bold');
        else
            set(colorTitleHandle ,'String','Fluxes','FontWeight','Bold');
        end
    end
end

set(gca,'XTickLabel',{' '});
set(gca,'YTickLabel',{' '});
set(gca,'YTick',[]);
set(gca,'XTick',[]);
set(gca,'XColor', 'none','YColor','none');
end


function trainingModel = loadTrainingData(param)
% Generates the structure that contains all the training data needed for
% Component Contribution.
%
% USAGE:
%
%    trainingModel = loadTrainingData(formation_weight)
%
% INPUT:
%    formation_weight:    the relative weight to give the formation energies (Alberty's data)
%                         compared to the reaction measurements (TECRDB)
%
% OUTPUT:
%    trainingModel:       structure with data for Component Contribution
%                         *.S   `m x n` stoichiometric matrix of training data
%                         *.cids: `m x 1` compound ids
%                         *.dG0_prime: `n x 1`
%                         *.T:  `n x 1`
%                         *.I:  `n x 1`
%                         *.pH:  `n x 1`
%                         *.pMg:  `n x 1`
%                         *.weights:  `n x 1`
%                         *.balance:  `n x 1`
%                         *.cids_that_dont_decompose: k x 1 ids of compounds that do not decompose

if ~exist('param','var')
    formation_weight = 1;
    use_cached_kegg_inchis=true;
    use_model_pKas_by_default=true;
else
    if ~isfield(param,'formation_weight')
        formation_weight = 1;
    end
    if ~isfield(params,'use_cached_kegg_inchis')
        use_cached_kegg_inchis = true;
        % use_cached_kegg_inchis = false;
    else
        use_cached_kegg_inchis=params.use_cached_kegg_inchis;
    end
    if ~isfield(params,'use_model_pKas_by_default')
        use_model_pKas_by_default = true;
    else
        use_model_pKas_by_default=params.use_model_pKas_by_default;
    end
end

TECRDB_TSV_FNAME = 'data/TECRDB.tsv';
FORMATION_TSV_FNAME = 'data/formation_energies_transformed.tsv';
REDOX_TSV_FNAME = 'data/redox.tsv';

WEIGHT_TECRDB = 1;
WEIGHT_FORMATION = formation_weight;
WEIGHT_REDOX = formation_weight;

R=8.31451;
%Energies are expressed in kJ mol^-1.*)
R=R/1000; % kJ/mol/K
%Faraday Constant (kJ/mol)
F=96.48; %kJ/mol

if ~exist(TECRDB_TSV_FNAME, 'file')
    error(['file not found: ', TECRDB_TSV_FNAME]);
end

if ~exist(FORMATION_TSV_FNAME, 'file')
    error(['file not found: ', FORMATION_TSV_FNAME]);
end

if ~exist(REDOX_TSV_FNAME, 'file')
    error(['file not found: ', REDOX_TSV_FNAME]);
end

% Read the raw data of TECRDB (NIST)
reactions = {};
rxns={};
cids = [];
cids_that_dont_decompose = [];
thermo_params = []; % columns are: dG'0, T, I, pH, pMg, weight, balance?

fid = fopen(TECRDB_TSV_FNAME, 'r');
% fields are:
% 1. URL
% 2. REF_ID
% 3. METHOD
% 4. EVAL
% 5. EC
% 6. ENZYME NAME
% 7. REACTION IN KEGG IDS
% 8. REACTION IN COMPOUND NAMES
% 9. K
% 10. K'
% 11. T
% 12. I
% 13. pH
% 14. pMg
% 15. ID

res = textscan(fid, '%s%s%s%s%s%s%s%s%f%f%f%f%f%f%f', 'delimiter','\t');
fclose(fid);

inds = find(~isnan(res{10}) .* ~isnan(res{11}) .* ~isnan(res{13}));

dG0_prime = -R * res{11}(inds) .* log(res{10}(inds)); % calculate dG'0
thermo_params = [dG0_prime, res{11}(inds), res{12}(inds), res{13}(inds), ...
                 res{14}(inds), WEIGHT_TECRDB * ones(size(inds)), ...
                 true(size(inds))];

% parse the reactions in each row
for i = 1:length(inds)
    sprs = reaction2sparse(res{7}{inds(i)});
    cids = unique([cids, find(sprs)]);
    reactions = [reactions, {sprs}];
    rxns = [rxns;strtrim(['TECRDB_' int2str(res{15}(inds(i)))])];
    if strcmp('TECRDB_4403',strtrim(['TECRDB_' int2str(res{15}(inds(i)))]))
        pause(0.1);
    end
end
fprintf('Successfully added %d values from TECRDB\n', length(inds));

% Read the Formation Energy data.
fid = fopen(FORMATION_TSV_FNAME, 'r');
fgetl(fid); % skip the first header line
% fields are:
% 1. cid
% 2. name
% 3. dG'0
% 4. pH
% 5. I
% 6. pMg
% 7. T
% 8. decompose?
% 9. compound_ref
% 10. remark

res = textscan(fid, '%f%s%f%f%f%f%f%f%s%s', 'delimiter','\t');
fclose(fid);

inds = find(~isnan(res{3}));
thermo_params = [thermo_params; [res{3}(inds), res{7}(inds), res{5}(inds), ...
                                 res{4}(inds), res{6}(inds), ...
                                 WEIGHT_FORMATION * ones(size(inds)), ...
                                 false(size(inds))]];

eval(['cid = {' regexprep(sprintf('''C%05d''; ',res{1}),'(;\s)$','') '};']);
for i = 1:length(inds)
    sprs = sparse([]);
    sprs(res{1}(inds(i))) = 1;
    reactions = [reactions, {sprs}];
    rxns = [rxns;['FORM_' cid{i}]];
end

cids = union(cids, res{1}');
cids_that_dont_decompose = res{1}(find(res{8} == 0));

fprintf('Successfully added %d formation energies\n', length(res{1}));

% Read the Reduction potential data.
fid = fopen(REDOX_TSV_FNAME, 'r');
fgetl(fid); % skip the first header line
% fields are:
% 1. name
% 2. CID_ox
% 3. nH_ox
% 4. charge_ox
% 5. CID_red
% 6. nH_red,
% 7. charge_red
% 8. E'0
% 9. pH
% 10. I
% 11. pMg
% 12. T
% 13. ref
res = textscan(fid, '%s%f%f%f%f%f%f%f%f%f%f%f%s', 'delimiter', '\t');
fclose(fid);

delta_e = (res{6} - res{3}) - (res{7} - res{4}); % delta_nH - delta_charge
dG0_prime = -F * res{8} .* delta_e;
thermo_params = [thermo_params; [dG0_prime, res{12}, res{10}, res{9}, ...
                                 res{11}, ...
                                 WEIGHT_REDOX * ones(size(dG0_prime)), ...
                                 false(size(dG0_prime))]];

for i = 1:length(res{1})
    sprs = sparse([]);
    sprs(res{2}(i)) = -1;
    sprs(res{5}(i)) = 1;
    cids = unique([cids, res{2}(i), res{5}(i)]);
    reactions = [reactions, {sprs}];
    rxns = [rxns;['REDOX_' strrep(res{1}{i},' ','_')]];
end

fprintf('Successfully added %d redox potentials\n', length(res{1}));

% convert the list of reactions in sparse notation into a full
% stoichiometric matrix, where the rows (compounds) are according to the
% CID list 'cids'.
S = zeros(length(cids), length(reactions));
for i = 1:length(reactions)
    r = reactions{i};
    S(ismember(cids, find(r)), i) = r(r ~= 0);
    %production of h20 in certain redox reactions
    if any(strcmp(rxns{i},{'REDOX_dimethyl_sulfoxide';'REDOX_Trimethylamine'}))
        S(cids==1,i)=1;
    end
end

trainingModel.S = sparse(S);

if ~isfield(trainingModel,'rxns')
    for i=1:size(trainingModel.S,2)
        trainingModel.rxns{i,1}=['rxn' int2str(i)];
    end
end
if ~isfield(trainingModel,'lb')
    trainingModel.lb=ones(size(trainingModel.S,2),1)*-inf;
end
if ~isfield(trainingModel,'ub')
    trainingModel.ub=ones(size(trainingModel.S,2),1)*inf;
end

trainingModel.rxns = rxns;
trainingModel.cids = cids';
trainingModel.dG0_prime = thermo_params(:, 1);
trainingModel.T = thermo_params(:, 2);
trainingModel.I = thermo_params(:, 3);
trainingModel.pH = thermo_params(:, 4);
trainingModel.pMg = thermo_params(:, 5);
trainingModel.weights = thermo_params(:, 6);
trainingModel.balance = thermo_params(:, 7);
trainingModel.cids_that_dont_decompose = cids_that_dont_decompose;

%remove some problematic entries
boolRemove=false(size(trainingModel.rxns,1),1);

%http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=66DED_421	C00089 + C06215 = C00031 + C06215	sucrose(aq) + (2,6--D-fructosyl)n(aq) = D-glucose(aq) + (2,6--D-fructosyl)n+1(aq)
%C06215 present on both sides of the reaction
%Need proper ID's for (2,6--D-fructosyl)n(aq) and (2,6--D-fructosyl)n+1(aq)
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_695');

% http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=93AND/BUL_1509	93AND/BUL	radioactivity	B	2.3.1.129	UDP-N-acetylglucosamine acyltransferase	C04688 + C00043 = C03688 + C04738	(R)-3-hydroxytetradecanoyl-[acyl-carrier-protein](aq) + UDP-N-acetyl-D-glucosamine(aq) = acyl-carrier-protein(aq) + UDP-3-O-(3-hydroxytetradecanoyl)-N-acetyl-D-glucosamine(aq)		0.007	296.15		7.4		2905
% http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=93AND/BUL_1509	93AND/BUL	radioactivity	B	2.3.1.129	UDP-N-acetylglucosamine acyltransferase	C04688 + C00043 = C03688 + C04738	(R)-3-hydroxytetradecanoyl-[acyl-carrier-protein](aq) + UDP-N-acetyl-D-glucosamine(aq) = acyl-carrier-protein(aq) + UDP-3-O-(3-hydroxytetradecanoyl)-N-acetyl-D-glucosamine(aq)		0.004	296.15		8.5		2906
% http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=93AND/BUL_1509	93AND/BUL	radioactivity	B	2.3.1.129	UDP-N-acetylglucosamine acyltransferase	C04688 + C00043 = C03688 + C04738	(R)-3-hydroxytetradecanoyl-[acyl-carrier-protein](aq) + UDP-N-acetyl-D-glucosamine(aq) = acyl-carrier-protein(aq) + UDP-3-O-(3-hydroxytetradecanoyl)-N-acetyl-D-glucosamine(aq)		0.002	296.15		9		2907
% http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=93AND/BUL_1509	93AND/BUL	radioactivity	B	2.3.1.129	UDP-N-acetylglucosamine acyltransferase	C04688 + C00043 = C03688 + C04738	(R)-3-hydroxytetradecanoyl-[acyl-carrier-protein](aq) + UDP-N-acetyl-D-glucosamine(aq) = acyl-carrier-protein(aq) + UDP-3-O-(3-hydroxytetradecanoyl)-N-acetyl-D-glucosamine(aq)		0.0095	296.15		8		2908
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_2905');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_2906');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_2907');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_2908');

%These were missing water on the rhs, fixed in TECRDB.tsv
%TECRDB_4540 85LIE		A	4.3.-.-	formaldehyde condensation with THF	C00101 + C00067 = C00143 + C00001	THF(aq) + formaldehyde(aq) = 5,10-CH2-THF(aq) + H2O(l)		30000	311.15	0.25	7		4540
%TECRDB_4541 +66KAL/JEN		E	4.3.-.-	formaldehyde condensation with THF	C00101 + C00067 = C00143 + C00001	THF(aq) + formaldehyde(aq) = 5,10-CH2-THF(aq) + H2O(l)		32000	298.15	1	7		4541
%TECRDB_4544 +59BLA		E	4.3.-.-	formaldehyde condensation with THF	C00101 + C00067 = C00143 + C00001	THF(aq) + formaldehyde(aq) = 5,10-CH2-THF(aq)		7700	293.15		7.2		4544

%The record of 85LIE is problematic i.e. TECRDB_870 - TECRDB_884
%e.g. http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=85LIE_292	85LIE	spectrophotometry and enzymatic assay	A	1.4.4.2	glycine dehydrogenase (decarboxylating)	C00037 + C00725 = C80069 + C00288	glycine(aq) + lipoate(aq) = S-aminomethyldihydro--lipoate(aq) + carbon dioxide(aq)		0.031	311.15		6.39		870
%is C00725 really C00248 ???
%C80069 is InChI=1S/C9H19NO2S2/c10-7-14-6-5-8(13)3-1-2-4-9(11)12/h8,13H,1-7,10H2,(H,11,12)
%C80069	but is it  InChI=1S/C9H20N2OS2/c10-7-14-6-5-8(13)3-1-2-4-9(11)12/h8,13H,1-7,10H2,(H2,11,12)/p+1  ???
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_870');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_871');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_872');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_873');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_874');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_875');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_876');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_877');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_878');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_879');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_880');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_881');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_882');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_883');
boolRemove = boolRemove | ismember(trainingModel.rxns,'TECRDB_884');
% The overall reaction reported in the abstract of the dissertation (https://digitalcommons.library.tmc.edu/dissertations/AAI8516325/) is as follows:
%85LIE		A	1.1.-.-	glycine cleavage system (a series of enzymes)	C00037 + C00003 + C00101 = C00143 + C00004 + C00014 + C00011 	glycine(aq) + NAD+(aq) + THF(aq) = 5,10-CH2-THF(aq) + NADH(aq) + NH3(aq) + CO2(aq)		0.00156	311.15	0.25	7		4539

%http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=65STR_520	65STR	spectrophotometry	C	2.6.1.13	ornithine-oxo-acid transaminase	C00077 + C00026 = C03912 + C00025	L-ornithine(aq) + 2-oxoglutarate(aq) = DL-D-1-pyrroline-5-carboxylate(aq) + L-glutamate(aq)		71	310.15		7.1		2719
%is http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=65STR_520	65STR	spectrophotometry	C	2.6.1.13	ornithine-oxo-acid transaminase	C00077 + C00026 = C01165 + C00025 + C00001	L-ornithine(aq) + 2-oxoglutarate(aq) = DL-D-1-pyrroline-5-carboxylate(aq) + L-glutamate(aq)		71	310.15		7.1		2719
%i.e. C00001 added to rhs, it is balanced, but is this correct?

%85LIE		A	1.1.-.-	glycine cleavage system	C00037 + C00003 + C00101 = C00143 + C00004 + C00014 + C00288 	glycine(aq) + NAD+(aq) + THF(aq) = 5,10-CH2-THF(aq) + NADH(aq) + NH3(aq) + CO2(aq)		0.00156	311.15	0.25	7		4539
%is 85LIE		A	1.1.-.-	glycine cleavage system	C00037 + C00003 + C00101 = C00143 + C00004 + C00014 + C00011 	glycine(aq) + NAD+(aq) + THF(aq) = 5,10-CH2-THF(aq) + NADH(aq) + NH3(aq) + CO2(aq)		0.00156	311.15	0.25	7		4539
%i.e. C00288 replaced by C00011

%http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=07LIN/ALG_1584	07LIN/ALG	spectrophotometry	A	1.1.1.87	homoisocitrate dehydrogenase	 C05662 + C00003 = C00322 + C00288 + C00004	(1R,2S)-1-hydroxybutane-1,2,4-tricarboxylate(aq) + NAD(ox) = 2-oxoadipate(aq) + carbon dioxide(aq) + NAD(red)		0.45	298.15		7.5		1
%is http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=07LIN/ALG_1584	07LIN/ALG	spectrophotometry	A	1.1.1.87	homoisocitrate dehydrogenase	C00001 + C05662 + C00003 = C00322 + C00288 + C00004	H2O(l) + (1R,2S)-1-hydroxybutane-1,2,4-tricarboxylate(aq) + NAD(ox) = 2-oxoadipate(aq) + carbon dioxide(aq) + NAD(red)		0.45	298.15		7.5		1
%i.e. lhs missing h20

%http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=95PEL/MAC_1595	95PEL/MAC	spectrophotometry and enzymatic assay	B	1.5.1.5	methylenetetrahydrofolate dehydrogenase (NADP+)	C00143 + C00006 = C00234 + C00005	5,10-methylenetetrahydrofolate(aq) + NADP(ox)(aq) = 10-formyltetrahydrofolate(aq) + NADP(red)(aq)		16	303.15		7.3		267
%is http://xpdb.nist.gov/enzyme_thermodynamics/enzyme_data1.pl?col=1.&T1=95PEL/MAC_1595	95PEL/MAC	spectrophotometry and enzymatic assay	B	1.5.1.5	methylenetetrahydrofolate dehydrogenase (NADP+)	C00001 + C00143 + C00006 = C00234 + C00005	H2O(l) + 5,10-methylenetetrahydrofolate(aq) + NADP(ox)(aq) = 10-formyltetrahydrofolate(aq) + NADP(red)(aq)		16	303.15		7.3		267
%i.e. lhs missing h20

%remove some of the training reactions that are problematic, e.g. those involving ACP
trainingModel.S = trainingModel.S(:,~boolRemove);
trainingModel.lb = trainingModel.lb(~boolRemove);
trainingModel.ub = trainingModel.ub(~boolRemove);
trainingModel.rxns = trainingModel.rxns(~boolRemove);
trainingModel.dG0_prime = trainingModel.dG0_prime(~boolRemove);
trainingModel.T = trainingModel.T(~boolRemove);
trainingModel.I = trainingModel.I(~boolRemove);
trainingModel.pH = trainingModel.pH(~boolRemove);
trainingModel.pMg = trainingModel.pMg(~boolRemove);
trainingModel.weights = trainingModel.weights(~boolRemove);
trainingModel.balance = trainingModel.balance(~boolRemove);