% Analyzes families of whole-cell simulations
%
% Author: Jonathan Karr, jkarr@stanford.edu
% Affiliation: Covert Lab, Department of Bioengineering, Stanford University
% Created: 11/1/2012
classdef MultiGenerations
    properties (Constant = true)
        ANCESTRY_COLIDX_CELL = 1
        ANCESTRY_COLIDX_GEN = 2
        ANCESTRY_COLIDX_FAMILY = 3
        ANCESTRY_COLIDX_PARENT = 4
        ANCESTRY_COLIDX_CHILD1 = 5
        ANCESTRY_COLIDX_CHILD2 = 6
    end

    methods (Static = true)
        function run(simBatchDir, nGen, nCellFirstGen)
            %% import
            import edu.stanford.covert.cell.sim.analysis.MultiGenerations;
            import edu.stanford.covert.cell.sim.util.CachedSimulationObjectUtil;
            import edu.stanford.covert.cell.sim.util.PlotUtil;
            import edu.stanford.covert.cell.sim.util.SimulationDiskUtil;
            import edu.stanford.covert.cell.sim.util.SimulationEnsemble;

            %%
            if nargin < 3
                simBatchDir = '2012_11_15_18_48_23';
                nGen = 3;
                nCellFirstGen = 8;
            end

            outDir = [SimulationDiskUtil.getBaseDir() filesep simBatchDir];

            if ~exist(outDir, 'dir')
                mkdir(outDir)
            end

            %% load constants
            sim = CachedSimulationObjectUtil.load();
            massState = sim.state('Mass');
            pm = sim.state('ProteinMonomer');
            pc = sim.state('ProteinComplex');

            %% load data
            ancestry = MultiGenerations.calcAncestry(nGen, nCellFirstGen);
            relations = MultiGenerations.calcAncestryRelations(nGen, nCellFirstGen);

            [~, simData, simStartTimes, divSimTfs, finSimTfs, finSimIdxs, propNames, ...
                dnaBndMons, dnaBndCpxs, otherBndCpxs, dnaBndMonTfs, dnaBndCpxTfs, otherBndCpxTfs] = ...
                MultiGenerations.cacheSimData(simBatchDir);
            simData = simData{1};
            simStartTimes = simStartTimes{1};
            divSimTfs = divSimTfs{1};
            finSimTfs = finSimTfs{1};
            finSimIdxs = finSimIdxs{1};
            dnaBndMons = dnaBndMons{1};
            dnaBndCpxs = dnaBndCpxs{1};
            otherBndCpxs = otherBndCpxs{1};
            dnaBndMonTfs = dnaBndMonTfs{1};
            dnaBndCpxTfs = dnaBndCpxTfs{1};
            otherBndCpxTfs = otherBndCpxTfs{1};

            %% population growth
            %growth traces
            for iFamily = 1:nCellFirstGen
                famIdxs = find(finSimTfs & ancestry(:, MultiGenerations.ANCESTRY_COLIDX_FAMILY) == iFamily);
                colors = MultiGenerations.calcRedGreenColors(numel(famIdxs));
                [~, order] = sort(simStartTimes(famIdxs));
                colors(order, :) = colors;
                tStep = 10;

                [axesHandle, figHandle] = PlotUtil.newAxesHandle();
                hold(axesHandle, 'on');
                for i = 1:numel(famIdxs)
                    tmp = SimulationEnsemble.load(simBatchDir, {'Time' 'values'; 'MetabolicReaction', 'growth'}, [], [], tStep, 'extract', famIdxs(i));
                    tmpTime = permute(tmp.Time.values, [1 3 2]) / 3600;
                    tmpGrowth = permute(tmp.MetabolicReaction.growth, [1 3 2]) * ...
                        massState.cellInitialDryWeight / (1 - massState.fractionWetWeight) * 3600 * 1e15;
                    plot(axesHandle, simStartTimes(famIdxs(i)) + tmpTime, tmpGrowth, ...
                        'Color', colors(i, :));
                end
                xlabel(axesHandle, 'Time (h)');
                ylabel(axesHandle, 'Growth (fg h^{-1})');
                saveas(figHandle, [outDir filesep 'Growth-Family-' num2str(iFamily) '.pdf']);
                close(figHandle);
            end

            %number cells
            nCells = NaN(nGen, 1);
            nSurvive = NaN(nGen, 1);
            for iGen = 0:nGen-1
                nCells(iGen + 1) = sum(ancestry(:, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen & finSimTfs);
                nSurvive(iGen + 1) = sum(ancestry(:, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen & divSimTfs);
            end
            fracSurvive = nSurvive ./ nCells;

            [axesHandle, figHandle] = PlotUtil.newAxesHandle();
            hold(axesHandle, 'on');

            plot(axesHandle, 0:nGen-1, nCells, 'b.')
            xlabel(axesHandle, 'Generation');
            ylabel(axesHandle, 'Population');
            xlim(axesHandle, [-0.5 nGen-0.5]);
            ylim(axesHandle, [0 75]);

            expFunc = @(a, b, x) a * exp(x / b);
            [f, gof] = fit((0:nGen-1)', nCells, ...
                fittype(expFunc),  ...
                fitoptions('Method', 'NonlinearLeastSquares', 'lower', [0.5 * nCellFirstGen  0.5], 'upper', [2.0 * nCellFirstGen  3], 'StartPoint', [nCellFirstGen 1/log(2)]));
            plot(axesHandle, (0:0.1:nGen-1)', expFunc(f.a, f.b, (0:0.1:nGen-1)'), 'Color', 'r');

            saveas(figHandle, [outDir filesep 'PopulationSize.pdf']);
            close(figHandle);

            %% survival
            %all families
            [axesHandle, figHandle] = PlotUtil.newAxesHandle();
            hold(axesHandle, 'on');

            errorbar(0:nGen-1, fracSurvive * 100, sqrt(fracSurvive .* (1 - fracSurvive)) ./ sqrt(nCells) * 100, 'b.', 'Parent', axesHandle);
            xlabel(axesHandle, 'Generation');
            ylabel(axesHandle, 'Survival (%)');
            xlim(axesHandle, [-0.5 nGen-0.5]);
            ylim(axesHandle, [0 100])

            [f, gof] = fit((0:nGen-1)', fracSurvive * 100, ...
                fittype('poly1'), ...
                fitoptions('Method', 'LinearLeastSquares', 'Weights', sqrt(fracSurvive .* (1 - fracSurvive)) ./ sqrt(nCells) * 100));
            plot(axesHandle, (0:0.1:nGen-1)', (0:0.1:nGen-1)' * f.p1 + f.p2, 'Color', 'r');

            saveas(figHandle, [outDir filesep 'Survival.pdf']);
            close(figHandle);

            %left/right
            nCellsLR = NaN(nGen, 2);
            nSurviveLR = NaN(nGen, 2);
            for iGen = 0:nGen-1
                nCellsLR(iGen + 1, 1) = sum(ancestry(1:2:end, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen & finSimTfs(1:2:end, 1));
                nCellsLR(iGen + 1, 2) = sum(ancestry(2:2:end, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen & finSimTfs(2:2:end, 1));
                nSurviveLR(iGen + 1, 1) = sum(ancestry(1:2:end, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen & divSimTfs(1:2:end, 1));
                nSurviveLR(iGen + 1, 2) = sum(ancestry(2:2:end, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen & divSimTfs(2:2:end, 1));
            end
            fracSurviveLR = nSurviveLR ./ nCellsLR;

            [axesHandle, figHandle] = PlotUtil.newAxesHandle();

            h = plot(axesHandle, 1:nGen-1, fracSurviveLR(2:end, :) * 100);
            legend(h, {'Left', 'Right'}, 'Location', 'NorthEastOutside');
            xlabel(axesHandle, 'Generation');
            ylabel(axesHandle, 'Survival (%)');
            xlim(axesHandle, [0.5 nGen-0.5]);
            ylim(axesHandle, [0 100])

            saveas(figHandle, [outDir filesep 'Survival-LeftRight.pdf']);
            close(figHandle);

            %by family
            [axesHandle, figHandle] = PlotUtil.newAxesHandle();
            hold(axesHandle, 'on');

            colors = MultiGenerations.calcRedGreenColors(nCellFirstGen);
            labels = cell(nCellFirstGen, 1);
            h = zeros(nCellFirstGen, 1);
            for i = 1:nCellFirstGen
                tfs = finSimTfs & ancestry(:, MultiGenerations.ANCESTRY_COLIDX_FAMILY) == i;
                fracSurvive = zeros(nGen, 1);
                for iGen = 0:nGen-1
                    fracSurvive(iGen + 1) = ...
                        sum(ancestry(divSimTfs & tfs, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen) / ...
                        sum(ancestry(tfs, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen);
                end
                h(i) = plot(axesHandle, 0:nGen-1, fracSurvive * 100, 'Color', colors(i, :));
                labels{i} = num2str(i);
            end

            legend(h, labels, 'Location', 'NorthEastOutside');
            xlabel(axesHandle, 'Generation');
            ylabel(axesHandle, 'Survival (%)');
            xlim(axesHandle, [-0.5 nGen-0.5]);
            ylim(axesHandle, [0 100])

            saveas(figHandle, [outDir filesep 'Survival-ByFamily.pdf']);
            close(figHandle);

            %% left/right chromosome bias
            %- bound protein
            %- methylation
            %- superhelical density

            gen_dnaBndMons = NaN(nGen, 2, sum(dnaBndMonTfs));
            gen_dnaBndCpxs = NaN(nGen, 2, sum(dnaBndCpxTfs));
            gen_otherBndCpxs = NaN(nGen, 2, sum(otherBndCpxTfs));

            for iGen = 0:nGen - 1
                lefts  = find( isodd(finSimIdxs) & ancestry(finSimIdxs, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen);
                rights = find(~isodd(finSimIdxs) & ancestry(finSimIdxs, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen);

                gen_dnaBndMons(iGen+1, 1, :) = mean(dnaBndMons(lefts, :), 1);
                gen_dnaBndMons(iGen+1, 2, :) = mean(dnaBndMons(rights, :), 1);

                gen_dnaBndCpxs(iGen+1, 1, :) = mean(dnaBndCpxs(lefts, :), 1);
                gen_dnaBndCpxs(iGen+1, 2, :) = mean(dnaBndCpxs(rights, :), 1);

                gen_otherBndCpxs(iGen+1, 1, :) = mean(otherBndCpxs(lefts, :), 1);
                gen_otherBndCpxs(iGen+1, 2, :) = mean(otherBndCpxs(rights, :), 1);
            end

            [axesHandle, figHandle] = PlotUtil.newAxesHandle();
            hold(axesHandle, 'on');
            h = plot(axesHandle, (1:nGen-1)', permute(gen_dnaBndMons(2:end, 1, :), [1 3 2]));
            g = plot(axesHandle, (1:nGen-1)', permute(gen_dnaBndMons(2:end, 2, :), [1 3 2]));
            colors = MultiGenerations.calcRedGreenColors(size(gen_dnaBndMons, 3));
            for i = 1:size(gen_dnaBndMons, 3)
                set(h(i), 'LineStyle', '-', 'Color', colors(i, :))
                set(g(i), 'LineStyle', ':', 'Color', colors(i, :))
            end
            legend(h, pm.wholeCellModelIDs(pm.boundIndexs(dnaBndMonTfs)), 'Location', 'NorthEastOutside', 'Interpreter', 'none');
            xlim(axesHandle, [0.5 nGen-0.5])
            xlabel(axesHandle, 'Generation');
            ylabel(axesHandle, 'Protein monomers')
            saveas(figHandle, [outDir filesep 'DNABoundMonomerLeftRightBias.pdf']);
            close(figHandle);

            [axesHandle, figHandle] = PlotUtil.newAxesHandle();
            hold(axesHandle, 'on');
            h = plot(axesHandle, (1:nGen-1)', permute(gen_dnaBndCpxs(2:end, 1, :), [1 3 2]));
            g = plot(axesHandle, (1:nGen-1)', permute(gen_dnaBndCpxs(2:end, 2, :), [1 3 2]));
            colors = MultiGenerations.calcRedGreenColors(size(gen_dnaBndCpxs, 3));
            for i = 1:size(gen_dnaBndCpxs, 3)
                set(h(i), 'LineStyle', '-', 'Color', colors(i, :))
                set(g(i), 'LineStyle', ':', 'Color', colors(i, :))
            end
            legend(h, pc.wholeCellModelIDs(pc.boundIndexs(dnaBndCpxTfs)), 'Location', 'NorthEastOutside', 'Interpreter', 'none');
            xlim(axesHandle, [0.5 nGen-0.5])
            xlabel(axesHandle, 'Generation');
            ylabel(axesHandle, 'Protein complexes')
            saveas(figHandle, [outDir filesep 'DNABoundComplexLeftRightBias.pdf']);
            close(figHandle);

            [axesHandle, figHandle] = PlotUtil.newAxesHandle();
            hold(axesHandle, 'on');
            h = plot(axesHandle, (1:nGen-1)', permute(gen_otherBndCpxs(2:end, 1, :), [1 3 2]));
            g = plot(axesHandle, (1:nGen-1)', permute(gen_otherBndCpxs(2:end, 2, :), [1 3 2]));
            colors = MultiGenerations.calcRedGreenColors(size(gen_otherBndCpxs, 3));
            for i = 1:size(gen_otherBndCpxs, 3)
                set(h(i), 'LineStyle', '-', 'Color', colors(i, :))
                set(g(i), 'LineStyle', ':', 'Color', colors(i, :))
            end
            legend(h, pc.wholeCellModelIDs(pc.boundIndexs(otherBndCpxTfs)), 'Location', 'NorthEastOutside', 'Interpreter', 'none');
            xlim(axesHandle, [0.5 nGen-0.5])
            xlabel(axesHandle, 'Generation');
            ylabel(axesHandle, 'Protein complexes')
            saveas(figHandle, [outDir filesep 'OtherBoundComplexLeftRightBias.pdf']);
            close(figHandle);

            %% growth, mass, cell cycle phase durations
            for iProp = 1:size(simData, 2)
                %all families
                [axesHandle, figHandle] = PlotUtil.newAxesHandle();
                boxplot(axesHandle, simData(finSimTfs, iProp), ancestry(finSimTfs, MultiGenerations.ANCESTRY_COLIDX_GEN));
                title(axesHandle, [propNames{iProp, 1} ' - All Cells'])
                xlabel(axesHandle, 'Generation');
                ylabel(axesHandle, propNames{iProp, 2});
                saveas(figHandle, [outDir filesep propNames{iProp, 1} 'BoxPlot-AllCells.pdf']);
                close(figHandle);

                [axesHandle, figHandle] = PlotUtil.newAxesHandle();
                boxplot(axesHandle, simData(divSimTfs, iProp), ancestry(divSimTfs, MultiGenerations.ANCESTRY_COLIDX_GEN));
                title(axesHandle, [propNames{iProp, 1} ' - Successful Divisions'])
                xlabel(axesHandle, 'Generation');
                ylabel(axesHandle, propNames{iProp, 2});
                saveas(figHandle, [outDir filesep propNames{iProp, 1} 'BoxPlot-SuccessfulCells.pdf']);
                close(figHandle);

                [axesHandle, figHandle] = PlotUtil.newAxesHandle();
                hold(axesHandle, 'on');
                h = zeros(nGen, 1);
                x = simData(finSimTfs, iProp);
                edges = linspace(min(x), max(x), 20);
                labels = cell(nGen, 1);
                colors = MultiGenerations.calcRedGreenColors(nGen);
                for iGen = 0:nGen-1
                    tfs = finSimTfs & ancestry(:, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen;
                    cnts = histc(simData(tfs, iProp), edges);
                    cnts = cnts / sum(tfs);
                    h(iGen + 1) = plot(axesHandle, edges, cnts, 'Color', colors(iGen + 1, :));
                    labels{iGen + 1} = num2str(iGen);
                end
                title(axesHandle, [propNames{iProp, 1} ' - All Cells'])
                xlabel(axesHandle, propNames{iProp, 2});
                ylabel(axesHandle, 'Frequency');
                legend(h, labels);
                saveas(figHandle, [outDir filesep propNames{iProp, 1} 'Histogram-AllCells.pdf']);
                close(figHandle);

                %left/right bias
                [axesHandle, figHandle] = PlotUtil.newAxesHandle();
                lefts = false(size(finSimTfs));
                lefts(1:2:end, 1) = true;
                yData = NaN(nGen, 1);
                for iGen = 0:nGen-1
                    yData(iGen + 1, 1) = nanmean(simData( lefts & finSimTfs & ancestry(:, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen, iProp));
                    yData(iGen + 1, 2) = nanmean(simData(~lefts & finSimTfs & ancestry(:, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen, iProp));
                end
                h = plot(axesHandle, 1:nGen-1, yData(2:end, :));
                legend(h, {'Left', 'Right'}, 'Location', 'NorthEastOutside');
                title(axesHandle, [propNames{iProp, 1}])
                xlabel(axesHandle, 'Generation');
                ylabel(axesHandle, propNames{iProp, 2});
                xlim(axesHandle, [0.5 nGen-0.5]);
                saveas(figHandle, [outDir filesep propNames{iProp, 1} 'MeanLeftRight.pdf']);
                close(figHandle);

                %by family
                [axesHandle, figHandle] = PlotUtil.newAxesHandle();
                hold(axesHandle, 'on');
                colors = MultiGenerations.calcRedGreenColors(nCellFirstGen);
                labels = cell(nCellFirstGen, 1);
                h = zeros(nCellFirstGen, 1);
                for i = 1:nCellFirstGen
                    tfs = finSimTfs & ancestry(:, MultiGenerations.ANCESTRY_COLIDX_FAMILY) == i;
                    h(i) = plot(axesHandle, ancestry(tfs, MultiGenerations.ANCESTRY_COLIDX_GEN), ...
                        simData(tfs, iProp), '.', 'Color', colors(i, :));

                    labels{i} = num2str(i);
                end
                legend(h, labels, 'Location', 'NorthEastOutside');
                title(axesHandle, [propNames{iProp, 1} ' - All Cells'])
                xlabel(axesHandle, 'Generation');
                ylabel(axesHandle, propNames{iProp, 2});
                xlim(axesHandle, [-0.5 nGen-0.5]);
                saveas(figHandle, [outDir filesep propNames{iProp, 1} 'Distribution.pdf']);
                close(figHandle);

                [axesHandle, figHandle] = PlotUtil.newAxesHandle();
                hold(axesHandle, 'on');
                colors = MultiGenerations.calcRedGreenColors(nCellFirstGen);
                labels = cell(nCellFirstGen, 1);
                h = zeros(nCellFirstGen, 1);
                for i = 1:nCellFirstGen
                    tfs = finSimTfs & ancestry(:, MultiGenerations.ANCESTRY_COLIDX_FAMILY) == i;
                    gens = ancestry(tfs, MultiGenerations.ANCESTRY_COLIDX_GEN);
                    x = simData(tfs, iProp);
                    avgs = zeros(nGen, 1);
                    for iGen = 0:nGen-1
                        avgs(iGen + 1) = nanmean(x(gens == iGen));
                    end
                    h(i) = plot(axesHandle, 0:nGen-1, avgs, 'Color', colors(i, :));

                    labels{i} = num2str(i);
                end
                legend(h, labels, 'Location', 'NorthEastOutside');
                title(axesHandle, [propNames{iProp, 1} ' - All Cells'])
                xlabel(axesHandle, 'Generation');
                ylabel(axesHandle, propNames{iProp, 2});
                xlim(axesHandle, [-0.25 nGen-0.75]);
                saveas(figHandle, [outDir filesep propNames{iProp, 1} 'MeanByLineage-AllCells.pdf']);
                close(figHandle);

                [axesHandle, figHandle] = PlotUtil.newAxesHandle();
                hold(axesHandle, 'on');
                colors = MultiGenerations.calcRedGreenColors(nCellFirstGen);
                labels = cell(nCellFirstGen, 1);
                h = zeros(nCellFirstGen, 1);
                for i = 1:nCellFirstGen
                    tfs = divSimTfs & ancestry(:, MultiGenerations.ANCESTRY_COLIDX_FAMILY) == i;
                    gens = ancestry(tfs, MultiGenerations.ANCESTRY_COLIDX_GEN);
                    x = simData(tfs, iProp);
                    avgs = zeros(nGen, 1);
                    for iGen = 0:nGen-1
                        avgs(iGen + 1) = nanmean(x(gens == iGen));
                    end
                    h(i) = plot(axesHandle, 0:nGen-1, avgs, 'Color', colors(i, :));

                    labels{i} = num2str(i);
                end
                legend(h, labels, 'Location', 'NorthEastOutside');
                title(axesHandle, [propNames{iProp, 1} ' - Successful Divisions'])
                xlabel(axesHandle, 'Generation');
                ylabel(axesHandle, propNames{iProp, 2});
                xlim(axesHandle, [-0.25 nGen-0.75]);
                saveas(figHandle, [outDir filesep propNames{iProp, 1} 'MeanByLineage-SuccessfulCells.pdf']);
                close(figHandle);
            end

            %% mother-daughter correlations within cell cycle
            deltaGenerations = relations(finSimTfs, finSimTfs, 1);
            removal = relations(finSimTfs, finSimTfs, 2);

            [idxs1, idxs2] = find(deltaGenerations == 1 & removal == 0);

            ensemble = SimulationEnsemble(simBatchDir, cell(0, 1), [1 2], find(finSimTfs));
            simEndTimes = ensemble.stateData.simulationEndTimes;
            states = SimulationEnsemble.load(simBatchDir, {'MetabolicReaction' 'growth'}, [], [], 1, 'extract', find(finSimTfs));
            growth = permute(states.MetabolicReaction.growth, [4 3 1 2]);
            growth(growth == 0) = NaN;
            growth0 = growth(sub2ind(size(growth), (1:numel(simEndTimes))', simEndTimes));

            r = zeros(1, size(growth, 2));
            n = zeros(1, size(growth, 2));
            for i = 1:size(growth, 2)
                tfs = ~isnan(growth(idxs2, i));
                r(i) = corr(growth0(idxs1(tfs)), growth(idxs2(tfs), i), 'type', 'Pearson');
                n(i) = sum(simEndTimes >= i);
            end

            [~, figHandle] = PlotUtil.newAxesHandle();
            clf(figHandle);

            axesHandle = subplot(2, 1, 1);
            plot(axesHandle, (1:size(growth, 2))/3600, r);
            xlabel(axesHandle, 'Time (h)');
            ylabel(axesHandle, 'Corr');

            axesHandle = subplot(2, 1, 2);
            plot(axesHandle, (1:size(growth, 2))/3600, n);
            xlabel(axesHandle, 'Time (h)');
            ylabel(axesHandle, 'No. cells');

            saveas(figHandle, [outDir filesep 'GrowthCorrelationDecay.pdf']);
            close(figHandle);

            %% descendents
            deltaGenerations = relations(:, :, 1);
            removal = relations(:, :, 2);

            colors = MultiGenerations.calcRedGreenColors(nGen - 1);
            for iProp = 1:size(propNames, 1)
                %all relationships
                [~, figHandle] = PlotUtil.newAxesHandle();
                clf(figHandle);

                axesHandle = subplot(2, 1, 1);
                hold(axesHandle, 'on');

                r = zeros(nGen - 1, 1);
                h = zeros(nGen - 1, 1);
                labels = cell(nGen - 1, 1);
                for iGen = 1:nGen-1
                    [idxs1, idxs2] = find(deltaGenerations == iGen & removal == 0);
                    tfs = finSimTfs(idxs1) & finSimTfs(idxs2);
                    idxs1 = idxs1(tfs, :);
                    idxs2 = idxs2(tfs, :);

                    r(iGen) = corr(simData(idxs1, iProp), simData(idxs2, iProp), 'type', 'Pearson');
                    h(iGen) = plot(simData(idxs1, iProp), simData(idxs2, iProp), '.', 'Color', colors(iGen, :));
                    labels{iGen} = sprintf('{\\Delta}g=%d (n=%d)', iGen, numel(idxs1));
                end

                legend(h, labels, 'Location', 'NorthEastOutside');
                title(axesHandle, ['All descendents - ' propNames{iProp, 1}])
                xlabel(axesHandle, 'Parent');
                ylabel(axesHandle, 'Child');

                axesHandle = subplot(2, 1, 2);
                plot(axesHandle, 1:nGen-1, r);
                line([0.5 nGen-0.5], [0 0], 'Color', 0.25 * [1 1 1], 'Parent', axesHandle);
                xlabel(axesHandle, '{\Delta}Generation');
                ylabel(axesHandle, 'Corr');
                xlim(axesHandle, [0.5 nGen-0.5]);
                ylim(axesHandle, [-1 1]);
                set(axesHandle, 'XTick', 1:nGen-1);
                box(axesHandle, 'off');

                saveas(figHandle, [outDir filesep 'Descendents-all-' propNames{iProp, 1} '.pdf']);
                close(figHandle);

                %relationships from founders
                [~, figHandle] = PlotUtil.newAxesHandle();
                clf(figHandle);

                axesHandle = subplot(2, 1, 1);
                hold(axesHandle, 'on');

                r = zeros(nGen - 1, 1);
                h = zeros(nGen - 1, 1);
                labels = cell(nGen - 1, 1);
                for iGen = 1:nGen-1
                    [idxs1, idxs2] = find(deltaGenerations == iGen & removal == 0);
                    tfs = finSimTfs(idxs1) & finSimTfs(idxs2);
                    tfs = tfs & ancestry(idxs1, MultiGenerations.ANCESTRY_COLIDX_GEN) == 0;
                    idxs1 = idxs1(tfs, :);
                    idxs2 = idxs2(tfs, :);

                    r(iGen) = corr(simData(idxs1, iProp), simData(idxs2, iProp), 'type', 'Pearson');
                    h(iGen) = plot(simData(idxs1, iProp), simData(idxs2, iProp), '.', 'Color', colors(iGen, :));
                    labels{iGen} = sprintf('{\\Delta}g=%d (n=%d)', iGen, numel(idxs1));
                end

                legend(h, labels, 'Location', 'NorthEastOutside');
                title(axesHandle, ['Generations from founder - ' propNames{iProp, 1}])
                xlabel(axesHandle, 'Parent');
                ylabel(axesHandle, 'Child');

                axesHandle = subplot(2, 1, 2);
                plot(axesHandle, 1:nGen-1, r);
                line([0.5 nGen-0.5], [0 0], 'Color', 0.25 * [1 1 1], 'Parent', axesHandle);
                xlabel(axesHandle, '{\Delta}Generation');
                ylabel(axesHandle, 'Corr');
                xlim(axesHandle, [0.5 nGen-0.5]);
                ylim(axesHandle, [-1 1]);
                set(axesHandle, 'XTick', 1:nGen-1);
                box(axesHandle, 'off');

                saveas(figHandle, [outDir filesep 'Descendents-FromFounders-' propNames{iProp, 1} '.pdf']);
                close(figHandle);

                %relationships from founders
                [~, figHandle] = PlotUtil.newAxesHandle();
                clf(figHandle);

                axesHandle = subplot(2, 1, 1);
                hold(axesHandle, 'on');

                r = zeros(nGen - 1, 1);
                h = zeros(nGen - 1, 1);
                labels = cell(nGen - 1, 1);
                for iGen = 1:nGen-1
                    [idxs1, idxs2] = find(deltaGenerations == 1 & removal == 0);
                    tfs = finSimTfs(idxs1) & finSimTfs(idxs2);
                    tfs = tfs & ancestry(idxs1, MultiGenerations.ANCESTRY_COLIDX_GEN) == (iGen - 1);
                    idxs1 = idxs1(tfs, :);
                    idxs2 = idxs2(tfs, :);

                    r(iGen) = corr(simData(idxs1, iProp), simData(idxs2, iProp), 'type', 'Pearson');
                    h(iGen) = plot(simData(idxs1, iProp), simData(idxs2, iProp), '.', 'Color', colors(iGen, :));
                    labels{iGen} = sprintf('g_1=%d (n=%d)', iGen, numel(idxs1));
                end

                legend(h, labels, 'Location', 'NorthEastOutside');
                title(axesHandle, propNames{iProp, 1})
                xlabel(axesHandle, 'Parent');
                ylabel(axesHandle, 'Child');

                axesHandle = subplot(2, 1, 2);
                plot(axesHandle, 1:nGen-1, r);
                line([0.5 nGen-0.5], [0 0], 'Color', 0.25 * [1 1 1], 'Parent', axesHandle);
                xlabel(axesHandle, '{\Delta}Generation');
                ylabel(axesHandle, 'Corr');
                xlim(axesHandle, [0.5 nGen-0.5]);
                ylim(axesHandle, [-1 1]);
                set(axesHandle, 'XTick', 1:nGen-1);
                box(axesHandle, 'off');

                saveas(figHandle, [outDir filesep 'Descendents-Children-' propNames{iProp, 1} '.pdf']);
                close(figHandle);
            end

            %% siblings, cousins
            deltaGenerations = relations(:, :, 1);
            removal = relations(:, :, 2);

            colors = MultiGenerations.calcRedGreenColors(nGen - 1);
            for iProp = 1:size(propNames, 1)
                %all cousins
                [~, figHandle] = PlotUtil.newAxesHandle();
                clf(figHandle);

                axesHandle = subplot(2, 1, 1);
                hold(axesHandle, 'on');

                r = zeros(nGen - 1, 1);
                h = zeros(nGen - 1, 1);
                labels = cell(nGen - 1, 1);
                for iGen = 1:nGen-1
                    [idxs1, idxs2] = find(deltaGenerations == 0 & removal == iGen);
                    tfs = finSimTfs(idxs1) & finSimTfs(idxs2);
                    idxs1 = idxs1(tfs, :);
                    idxs2 = idxs2(tfs, :);

                    r(iGen) = corr(simData(idxs1, iProp), simData(idxs2, iProp), 'type', 'Pearson');
                    h(iGen) = plot(simData(idxs1, iProp), simData(idxs2, iProp), '.', 'Color', colors(iGen, :));
                    labels{iGen} = sprintf('{\\Delta}r=%d (n=%d)', iGen, numel(idxs1));
                end

                legend(h, labels, 'Location', 'NorthEastOutside');
                title(axesHandle, ['All cousins - ' propNames{iProp, 1}])
                xlabel(axesHandle, 'Parent');
                ylabel(axesHandle, 'Child');

                axesHandle = subplot(2, 1, 2);
                plot(axesHandle, 1:nGen-1, r);
                line([0.5 nGen-0.5], [0 0], 'Color', 0.25 * [1 1 1], 'Parent', axesHandle);
                xlabel(axesHandle, 'Removal');
                ylabel(axesHandle, 'Corr');
                xlim(axesHandle, [0.5 nGen-0.5]);
                ylim(axesHandle, [-1 1]);
                set(axesHandle, 'XTick', 1:nGen-1);
                box(axesHandle, 'off');

                saveas(figHandle, [outDir filesep 'Cousins-all-' propNames{iProp, 1} '.pdf']);
                close(figHandle);

                %cousins of last generation
                [~, figHandle] = PlotUtil.newAxesHandle();
                clf(figHandle);

                axesHandle = subplot(2, 1, 1);
                hold(axesHandle, 'on');

                r = zeros(nGen - 1, 1);
                h = zeros(nGen - 1, 1);
                labels = cell(nGen - 1, 1);
                for iGen = 1:nGen-1
                    [idxs1, idxs2] = find(deltaGenerations == 0 & removal == iGen);
                    tfs = finSimTfs(idxs1) & finSimTfs(idxs2) & ancestry(idxs1, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen;
                    idxs1 = idxs1(tfs, :);
                    idxs2 = idxs2(tfs, :);

                    r(iGen) = corr(simData(idxs1, iProp), simData(idxs2, iProp), 'type', 'Pearson');
                    h(iGen) = plot(simData(idxs1, iProp), simData(idxs2, iProp), '.', 'Color', colors(iGen, :));
                    labels{iGen} = sprintf('{\\Delta}r=%d (n=%d)', iGen, numel(idxs1));
                end

                legend(h, labels, 'Location', 'NorthEastOutside');
                title(axesHandle, ['Cousins of last generation - ' propNames{iProp, 1}])
                xlabel(axesHandle, 'Parent');
                ylabel(axesHandle, 'Child');

                axesHandle = subplot(2, 1, 2);
                plot(axesHandle, 1:nGen-1, r);
                line([0.5 nGen-0.5], [0 0], 'Color', 0.25 * [1 1 1], 'Parent', axesHandle);
                xlabel(axesHandle, 'Removal');
                ylabel(axesHandle, 'Corr');
                xlim(axesHandle, [0.5 nGen-0.5]);
                ylim(axesHandle, [-1 1]);
                set(axesHandle, 'XTick', 1:nGen-1);
                box(axesHandle, 'off');

                saveas(figHandle, [outDir filesep 'Cousins-LastGeneration-' propNames{iProp, 1} '.pdf']);
                close(figHandle);

                %first cousins by generation
                [~, figHandle] = PlotUtil.newAxesHandle();
                clf(figHandle);

                axesHandle = subplot(2, 1, 1);
                hold(axesHandle, 'on');

                r = NaN(nGen - 1, 1);
                h = NaN(nGen - 1, 1);
                labels = cell(nGen - 1, 1);
                for iGen = 1:nGen-1
                    [idxs1, idxs2] = find(deltaGenerations == 0 & removal == 1);
                    tfs = finSimTfs(idxs1) & finSimTfs(idxs2);
                    tfs = tfs & ancestry(idxs1, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen;
                    if ~any(tfs)
                        continue;
                    end
                    idxs1 = idxs1(tfs, :);
                    idxs2 = idxs2(tfs, :);

                    r(iGen) = corr(simData(idxs1, iProp), simData(idxs2, iProp), 'type', 'Pearson');
                    h(iGen) = plot(simData(idxs1, iProp), simData(idxs2, iProp), '.', 'Color', colors(iGen, :));
                    labels{iGen} = sprintf('g=%d (n=%d)', iGen, numel(idxs1));
                end

                legend(h(~isnan(h)), labels(~isnan(h)), 'Location', 'NorthEastOutside');
                title(axesHandle, ['All First Cousins - ' propNames{iProp, 1}])
                xlabel(axesHandle, 'Parent');
                ylabel(axesHandle, 'Child');

                axesHandle = subplot(2, 1, 2);
                plot(axesHandle, 1:nGen-1, r);
                line([0.5 nGen-0.5], [0 0], 'Color', 0.25 * [1 1 1], 'Parent', axesHandle);
                xlabel(axesHandle, '{\Delta}Generation');
                ylabel(axesHandle, 'Corr');
                xlim(axesHandle, [0.5 nGen-0.5]);
                ylim(axesHandle, [-1 1]);
                set(axesHandle, 'XTick', 1:nGen-1);
                box(axesHandle, 'off');

                saveas(figHandle, [outDir filesep 'FirstCousins-all-' propNames{iProp, 1} '.pdf']);
                close(figHandle);
            end

            %% Varying effective DnaA copy number
            MultiGenerations.runEffectiveDnaACopyNumberAnalysis();
        end

        function runEffectiveDnaACopyNumberAnalysis(outDir)
            import edu.stanford.covert.cell.sim.analysis.MultiGenerations;
            import edu.stanford.covert.cell.sim.util.PlotUtil;
            import edu.stanford.covert.cell.sim.util.SimulationDiskUtil;

            [simBatchDirs, simData, ~, ~, finSimTfs, ~, propNames] = MultiGenerations.cacheSimData();

            nSimBatch = size(simBatchDirs, 1);

            %% create output directory
            if nargin < 1
                outDir = [SimulationDiskUtil.getBaseDir() filesep 'multiGenerations'];
            end
            if ~exist(outDir, 'dir')
                mkdir(outDir)
            end

            %% analyze
            colors = MultiGenerations.calcRedGreenColors(nSimBatch);
            for iProp = 1:size(propNames, 1)
                [axesHandle, figHandle] = PlotUtil.newAxesHandle();
                hold(axesHandle, 'on');

                h = zeros(nSimBatch, 1);
                for iSimBatch = 1:nSimBatch
                    nGen = simBatchDirs{iSimBatch, 2};
                    nCellFirstGen = simBatchDirs{iSimBatch, 3};
                    ancestry = MultiGenerations.calcAncestry(nGen, nCellFirstGen);

                    avgs = zeros(1, nGen);
                    for iGen = 0:nGen - 1
                        avgs(1, iGen + 1) = nanmean(simData{iSimBatch}(finSimTfs{iSimBatch} & ancestry(:, MultiGenerations.ANCESTRY_COLIDX_GEN) == iGen, iProp));
                    end
                    h(iSimBatch) = plot(axesHandle, 0:nGen-1, avgs, 'Color', colors(iSimBatch, :));
                end

                xlim(axesHandle, [-0.5 nGen-0.5]);
                title(axesHandle, ['DnaA Copy Number vs ' propNames{iProp, 1}]);
                xlabel(axesHandle, 'Generation');
                ylabel(axesHandle, propNames{iProp, 2});
                legend(h, cellfun(@num2str, simBatchDirs(:, 4), 'UniformOutput', false), 'Location', 'NorthEastOutside');

                saveas(figHandle, [outDir filesep 'DnaACopyNumberVs' propNames{iProp, 1} '.pdf']);
                close(figHandle);
            end
        end

        function [simBatchDirs, simData, simStartTimes, divSimTfs, finSimTfs, finSimIdxs, propNames, ...
                dnaBndMons, dnaBndCpxs, otherBndCpxs, dnaBndMonTfs, dnaBndCpxTfs, otherBndCpxTfs] = cacheSimData(simBatchDirs)
            import edu.stanford.covert.cell.sim.analysis.MultiGenerations;
            import edu.stanford.covert.cell.sim.util.CachedSimulationObjectUtil;
            import edu.stanford.covert.cell.sim.util.SimulationDiskUtil;

            if nargin >= 1
                if ~iscell(simBatchDirs)
                    simBatchDirs = {simBatchDirs};
                end
            else
                simBatchDirs = {
                    '2012_11_15_18_48_23'  3 8  3
                    '2012_10_24_00_49_53'  6 8  1
                    };
            end

            nSimBatch = size(simBatchDirs, 1);

            %% load constants
            sim = CachedSimulationObjectUtil.load();

            %% get data
            simData = cell(nSimBatch, 1);
            simStartTimes = cell(nSimBatch, 1);
            divSimTfs = cell(nSimBatch, 1);
            finSimTfs = cell(nSimBatch, 1);
            finSimIdxs = cell(nSimBatch, 1);
            dnaBndMons = cell(nSimBatch, 1);
            dnaBndCpxs = cell(nSimBatch, 1);
            otherBndCpxs = cell(nSimBatch, 1);
            dnaBndMonTfs = cell(nSimBatch, 1);
            dnaBndCpxTfs = cell(nSimBatch, 1);
            otherBndCpxTfs = cell(nSimBatch, 1);
            for iSimBatch = 1:nSimBatch
                simBatchDir = simBatchDirs{iSimBatch, 1};
                if exist([SimulationDiskUtil.getBaseDir() filesep simBatchDir filesep 'MultiGenerationsData.mat'], 'file')
                    tmp = load([SimulationDiskUtil.getBaseDir() filesep simBatchDir filesep 'MultiGenerationsData.mat']);
                else
                    nGen = simBatchDirs{iSimBatch, 2};
                    nCellFirstGen = simBatchDirs{iSimBatch, 3};

                    [tmp_simData, tmp_simStartTimes, tmp_divSimTfs, tmp_finSimTfs, tmp_finSimIdxs, tmp_propNames, ...
                        tmp_dnaBndMons, tmp_dnaBndCpxs, tmp_otherBndCpxs, tmp_dnaBndMonTfs, tmp_dnaBndCpxTfs, tmp_otherBndCpxTfs] = ...
                        MultiGenerations.getSimData(simBatchDir, nGen, nCellFirstGen, sim);

                    tmp = struct;
                    tmp.simData = tmp_simData;
                    tmp.simStartTimes = tmp_simStartTimes;
                    tmp.divSimTfs = tmp_divSimTfs;
                    tmp.finSimTfs = tmp_finSimTfs;
                    tmp.finSimIdxs = tmp_finSimIdxs;
                    tmp.propNames = tmp_propNames;
                    tmp.dnaBndMons = tmp_dnaBndMons;
                    tmp.dnaBndCpxs = tmp_dnaBndCpxs;
                    tmp.otherBndCpxs = tmp_otherBndCpxs;
                    tmp.dnaBndMonTfs = tmp_dnaBndMonTfs;
                    tmp.dnaBndCpxTfs = tmp_dnaBndCpxTfs;
                    tmp.otherBndCpxTfs = tmp_otherBndCpxTfs;
                    save([SimulationDiskUtil.getBaseDir() filesep simBatchDir filesep 'MultiGenerationsData.mat'], '-struct', 'tmp');
                end
                simData{iSimBatch} = tmp.simData;
                simStartTimes{iSimBatch} = tmp.simStartTimes;
                divSimTfs{iSimBatch} = tmp.divSimTfs;
                finSimTfs{iSimBatch} = tmp.finSimTfs;
                finSimIdxs{iSimBatch} = tmp.finSimIdxs;

                dnaBndMons{iSimBatch} = tmp.dnaBndMons;
                dnaBndCpxs{iSimBatch} = tmp.dnaBndCpxs;
                otherBndCpxs{iSimBatch} = tmp.otherBndCpxs;
                dnaBndMonTfs{iSimBatch} = tmp.dnaBndMonTfs;
                dnaBndCpxTfs{iSimBatch} = tmp.dnaBndCpxTfs;
                otherBndCpxTfs{iSimBatch} = tmp.otherBndCpxTfs;

                propNames = tmp.propNames;
            end
        end

        function [simData, simStartTimes, divSimTfs, finSimTfs, finSimIdxs, propNames, ...
                dnaBndMons, dnaBndCpxs, otherBndCpxs, ...
                dnaBndMonTfs, dnaBndCpxTfs, otherBndCpxTfs] = ...
                getSimData(simBatchDir, nGen, nCellFirstGen, sim)
            import edu.stanford.covert.cell.sim.analysis.MultiGenerations;
            import edu.stanford.covert.cell.sim.util.SimulationDiskUtil;
            import edu.stanford.covert.cell.sim.util.SimulationEnsemble;
            import edu.stanford.covert.cell.sim.util.SummaryLogger;

            ancestry = MultiGenerations.calcAncestry(nGen, nCellFirstGen);
            nCells = size(ancestry, 1);

            g = sim.gene;
            c = sim.compartment;
            massState = sim.state('Mass');
            met = sim.state('Metabolite');
            pm = sim.state('ProteinMonomer');
            pc = sim.state('ProteinComplex');

            simStats = SummaryLogger.getSimulationStatistics([SimulationDiskUtil.getBaseDir() filesep simBatchDir], 1:nCells);
            simStats(:, SummaryLogger.SIM_STATUS_INDEX_INIT_GROWTH) = ...
                simStats(:, SummaryLogger.SIM_STATUS_INDEX_INIT_GROWTH)' * ...
                massState.cellInitialDryWeight / (1 - massState.fractionWetWeight) * 3600 * 1e15;
            simStats(:, SummaryLogger.SIM_STATUS_INDEX_REP_INIT_TIME) = simStats(:, SummaryLogger.SIM_STATUS_INDEX_REP_INIT_TIME) / 3600;
            simStats(:, SummaryLogger.SIM_STATUS_INDEX_INIT_REP_TIME) = simStats(:, SummaryLogger.SIM_STATUS_INDEX_INIT_REP_TIME) / 3600;
            simStats(:, SummaryLogger.SIM_STATUS_INDEX_REP_TIME) = simStats(:, SummaryLogger.SIM_STATUS_INDEX_REP_TIME) / 3600;
            simStats(:, SummaryLogger.SIM_STATUS_INDEX_CYTOKINESIS_TIME) = simStats(:, SummaryLogger.SIM_STATUS_INDEX_CYTOKINESIS_TIME) / 3600;
            simStats(:, SummaryLogger.SIM_STATUS_INDEX_MASS_DOUBLING_TIME) = simStats(:, SummaryLogger.SIM_STATUS_INDEX_MASS_DOUBLING_TIME) / 3600;
            simStats(:, SummaryLogger.SIM_STATUS_INDEX_FIN_MASS) = simStats(:, SummaryLogger.SIM_STATUS_INDEX_FIN_MASS) * 1e15;
            simStats(:, SummaryLogger.SIM_STATUS_INDEX_INIT_MASS) = simStats(:, SummaryLogger.SIM_STATUS_INDEX_INIT_MASS) * 1e15;

            simStartTimes = zeros(size(ancestry, 1), 1);
            for i = 1:size(ancestry, 1)
                if isnan(ancestry(i, MultiGenerations.ANCESTRY_COLIDX_PARENT))
                    simStartTimes(i) = 0;
                else
                    iParent = ancestry(i, MultiGenerations.ANCESTRY_COLIDX_PARENT);
                    simStartTimes(i) = ...
                        simStartTimes(iParent) +  ...
                        simStats(iParent, SummaryLogger.SIM_STATUS_INDEX_CYTOKINESIS_TIME) + ...
                        1 / 3600;
                end

                if ~isnan(ancestry(i, MultiGenerations.ANCESTRY_COLIDX_CHILD1)) &&  ...
                        simStats(i, SummaryLogger.SIM_STATUS_INDEX_STATUS) ~= SummaryLogger.SIM_STATUS_COMPLETED_WITH_DIVISION
                    simStats(ancestry(i, MultiGenerations.ANCESTRY_COLIDX_CHILD1), SummaryLogger.SIM_STATUS_INDEX_STATUS) = SummaryLogger.SIM_STATUS_DIDNT_START;
                    simStats(ancestry(i, MultiGenerations.ANCESTRY_COLIDX_CHILD2), SummaryLogger.SIM_STATUS_INDEX_STATUS) = SummaryLogger.SIM_STATUS_DIDNT_START;
                end
            end

            divSimTfs = simStats(:, SummaryLogger.SIM_STATUS_INDEX_STATUS) == SummaryLogger.SIM_STATUS_COMPLETED_WITH_DIVISION;
            nonDivSimTfs = simStats(:, SummaryLogger.SIM_STATUS_INDEX_STATUS) == SummaryLogger.SIM_STATUS_COMPLETED_WITHOUT_DIVISION;
            finSimTfs = divSimTfs | nonDivSimTfs;
            finSimIdxs = find(finSimTfs);

            props = {'dnaA_total'; 'dnaA_boxes'; 'rnaPolymerases'; 'ribosomes';
                'amino_acids'; 'ntps'; 'rnas'; 'immatureRnas'; 'matureMonomers'; 'immatureMonomers';
                'matureComplexs'; 'immatureComplexs'; 'atp'; 'adp'; 'amp'};
            ensemble = SimulationEnsemble(simBatchDir, props, [1 2], finSimIdxs);

            rnaPolMonIdxs = pm.matureIndexs(any(pc.proteinComplexComposition(g.mRNAIndexs, pc.rnaPolymeraseIndexs(1), :), 3));
            stateNames = {
                'ProteinMonomer'  'counts'  rnaPolMonIdxs   c.cytosolIndexs
                };
            rnaPolSubunitCnts = SimulationEnsemble.load(simBatchDir, stateNames, 1, 1, 1, 'extract', finSimIdxs);

            stateNames = {
                'ProteinMonomer'  'counts'                pm.boundIndexs  c.cytosolIndexs
                'ProteinComplex'  'counts'                pc.boundIndexs  c.cytosolIndexs
                'Chromosome'      'damagedBases'          ':'  ':'
                'Chromosome'      'superhelicalDensity'   ':'  ':'
                };
            chromState = SimulationEnsemble.load(simBatchDir, stateNames, 1, 1, 1, 'extract', finSimIdxs);

            dnaBndMonTfs = any(any(any(chromState.ProteinMonomer.counts, 2), 3), 4);
            dnaBndCpxTfs = any(any(any(chromState.ProteinComplex.counts, 2), 3), 4) & ~any(any(pc.proteinComplexComposition(g.getIndexs('MG_469'), :, :), 1), 3)';
            otherBndCpxTfs = any(any(any(chromState.ProteinComplex.counts, 2), 3), 4) &  any(any(pc.proteinComplexComposition(g.getIndexs('MG_469'), :, :), 1), 3)';

            dnaBndMons = NaN(numel(finSimIdxs), sum(dnaBndMonTfs));
            dnaBndCpxs = NaN(numel(finSimIdxs), sum(dnaBndCpxTfs));
            otherBndCpxs = NaN(numel(finSimIdxs), sum(otherBndCpxTfs));
            methylations = NaN(numel(finSimIdxs), 1);
            sigmas = NaN(numel(finSimIdxs), 1);

            for i = 1:numel(finSimIdxs)
                dnaBndMons(i, :) = chromState.ProteinMonomer.counts(dnaBndMonTfs, 1, 1, i);
                dnaBndCpxs(i, :) = chromState.ProteinComplex.counts(dnaBndCpxTfs, 1, 1, i);
                otherBndCpxs(i, :) = chromState.ProteinComplex.counts(otherBndCpxTfs, 1, 1, i);
                methylations(i, 1) = full(sum(sum(chromState.Chromosome.damagedBases(:, :, 1, i) == met.m6ADIndexs, 1), 2));
                sigmas(i, 1) = full(chromState.Chromosome.superhelicalDensity(1, 1, 1, i));
            end

            simData = [
                simStats(:, SummaryLogger.SIM_STATUS_INDEX_INIT_GROWTH) ...
                simStats(:, SummaryLogger.SIM_STATUS_INDEX_FIN_MASS) ...
                simStats(:, SummaryLogger.SIM_STATUS_INDEX_REP_INIT_TIME) ...
                simStats(:, SummaryLogger.SIM_STATUS_INDEX_REP_TIME)-simStats(:, SummaryLogger.SIM_STATUS_INDEX_REP_INIT_TIME) ...
                simStats(:, SummaryLogger.SIM_STATUS_INDEX_CYTOKINESIS_TIME)-simStats(:, SummaryLogger.SIM_STATUS_INDEX_REP_TIME) ...
                simStats(:, SummaryLogger.SIM_STATUS_INDEX_CYTOKINESIS_TIME) ...
                simStats(:, SummaryLogger.SIM_STATUS_INDEX_INIT_MASS) ...
                simStats(:, SummaryLogger.SIM_STATUS_INDEX_FIN_MASS) ...
                zeros(size(simStats, 1), size(ensemble.stateData.values, 1) + size(rnaPolSubunitCnts.ProteinMonomer.counts, 1) + 2)
                ];
            simData(finSimTfs, 9:end) = [
                permute(ensemble.stateData.values(:, :, 1, :), [4 1 2 3]) ...
                permute(rnaPolSubunitCnts.ProteinMonomer.counts, [4 1 2 3]) ...
                methylations ...
                sigmas
                ];
            propNames = {
                'InitialGrowth'                   'Growth (fg h^{-1})'
                'FinalGrowth'                     'Growth (fg h^{-1})'
                'ReplicationInitiationDuration'   'Time (h)'
                'ReplicationDuration'             'Time (h)'
                'CytokinesisDuration'             'Time (h)'
                'CellCycleLength'                 'Time (h)'
                'InitialMass'                     'Mass (fg)'
                'FinalMass'                       'Mass (fg)'
                'DnaA'                            'Count'
                'BoundDnaA'                       'Count'
                'RNAPolymerases'                  'Count'
                'Ribosomes'                       'Count'
                'AminoAcids'                      'Count'
                'Ntps'                            'Count'
                'Rnas'                            'Count'
                'ImmatureRnas'                    'Count'
                'MatureMonomers'                  'Count'
                'ImmatureMonomers'                'Count'
                'MatureComplexes'                 'Count'
                'ImmatureComplexes'               'Count'
                'Atp'                             'Count'
                'Adp'                             'Count'
                'Amp'                             'Count'
                'RnaPolDelta'                     'Count'
                'RnaPolAlpha'                     'Count'
                'RnaPolBeta1'                     'Count'
                'RnaPolBeta2'                     'Count'
                'Methylations'                    'Count'
                'SuperhelicalDensity'             'dimensionless'
                };
        end

        %Returns matrix with five colums:
        %- cell index
        %- generation
        %- parent index
        %- child-1 index
        %- child-2 index
        function ancestry = calcAncestry(nGen, nCellFirstGen)
            import edu.stanford.covert.cell.sim.analysis.MultiGenerations;

            nCells = nCellFirstGen * (2^nGen - 1);

            ancestry = NaN(nCells, 5);

            genOffset = 0;
            iCell = 0;
            for iGen = 0:nGen-1
                for iGenCell = 1:nCellFirstGen * 2^iGen
                    iCell = iCell + 1;

                    if iGen == 0
                        iParent = NaN;
                        iFamily = iGenCell;
                    else
                        iParent = genOffset - nCellFirstGen * 2^(iGen-1) + ceil(iGenCell / 2);
                        iFamily = ancestry(iParent, MultiGenerations.ANCESTRY_COLIDX_FAMILY);
                    end

                    if iGen < nGen-1
                        iChild1 = genOffset + nCellFirstGen * 2^iGen + 2 * iGenCell - 1;
                        iChild2 = genOffset + nCellFirstGen * 2^iGen + 2 * iGenCell;
                    else
                        iChild1 = NaN;
                        iChild2 = NaN;
                    end

                    ancestry(iCell, MultiGenerations.ANCESTRY_COLIDX_CELL) = genOffset + iGenCell;
                    ancestry(iCell, MultiGenerations.ANCESTRY_COLIDX_GEN) = iGen;
                    ancestry(iCell, MultiGenerations.ANCESTRY_COLIDX_FAMILY) = iFamily;
                    ancestry(iCell, MultiGenerations.ANCESTRY_COLIDX_PARENT) = iParent;
                    ancestry(iCell, MultiGenerations.ANCESTRY_COLIDX_CHILD1) = iChild1;
                    ancestry(iCell, MultiGenerations.ANCESTRY_COLIDX_CHILD2) = iChild2;
                end
                genOffset = genOffset + nCellFirstGen * 2^iGen;
            end
        end

        function relations = calcAncestryRelations(nGen, nCellFirstGen)
            import edu.stanford.covert.cell.sim.analysis.MultiGenerations;

            nCells = 2^nGen - 1;

            %intergenerational -- difference in generations
            generations = NaN(nCells, 1);
            genOffset = 0;
            for iGen = 0:nGen-1
                iCell = genOffset + (1:2^iGen);
                genOffset = genOffset + 2^iGen;
                generations(iCell) = iGen;
            end
            deltaGeneration = -generations(:, ones(nCells, 1)) + generations(:, ones(nCells, 1))';

            %intragenerational -- number of generations since common ancestor
            ancestry = MultiGenerations.calcAncestry(nGen, 1);
            removal = NaN(nCells, nCells);
            for iCell1 = 1:nCells
                removal(iCell1, iCell1) = 0;
                for iCell2 = iCell1+1:nCells
                    [~, removal(iCell1, iCell2)] = MultiGenerations.calcCommonAncestor(iCell1, iCell2, ancestry);
                    removal(iCell2, iCell1) = removal(iCell1, iCell2);
                end
            end

            tmp = cat(3, deltaGeneration, removal);
            relations = NaN(nCellFirstGen * nCells, nCellFirstGen * nCells, 2); %cell-1 x cell-2 x [generation separation,  removal]
            ancestry = MultiGenerations.calcAncestry(nGen, nCellFirstGen);
            for i = 1:nCellFirstGen
                relations(ancestry(:, MultiGenerations.ANCESTRY_COLIDX_FAMILY) == i, ancestry(:, MultiGenerations.ANCESTRY_COLIDX_FAMILY) == i, :) = tmp;
            end
        end

        function [iCommonAncestor, removal] = calcCommonAncestor(iCell1, iCell2, ancestry)
            import edu.stanford.covert.cell.sim.analysis.MultiGenerations;

            if iCell1 == iCell2
                iCommonAncestor = iCell1;
                removal = 0;
            elseif ancestry(iCell1, MultiGenerations.ANCESTRY_COLIDX_GEN) > ancestry(iCell2, MultiGenerations.ANCESTRY_COLIDX_GEN)
                [iCommonAncestor, removal] = MultiGenerations.calcCommonAncestor(ancestry(iCell1, MultiGenerations.ANCESTRY_COLIDX_PARENT), iCell2, ancestry);
            elseif ancestry(iCell1, MultiGenerations.ANCESTRY_COLIDX_GEN) < ancestry(iCell2, MultiGenerations.ANCESTRY_COLIDX_GEN)
                [iCommonAncestor, removal] = MultiGenerations.calcCommonAncestor(iCell1, ancestry(iCell2, MultiGenerations.ANCESTRY_COLIDX_PARENT), ancestry);
            else
                [iCommonAncestor, removal] = MultiGenerations.calcCommonAncestor(...
                    ancestry(iCell1, MultiGenerations.ANCESTRY_COLIDX_PARENT), ...
                    ancestry(iCell2, MultiGenerations.ANCESTRY_COLIDX_PARENT), ...
                    ancestry);
                removal = removal + 1;
            end
        end

        function colors = calcRedGreenColors(n)
            tmp = (0:n-1)';
            r1 = 1;
            r2 = 0;
            g1 = 0;
            g2 = 1;
            b1 = 0;
            b2 = 0;
            colors = [
                r1*2*max(0, 0.5-tmp/(n-1)) + r2*2*max(0, tmp/(n-1)-0.5) ...
                g1*2*max(0, 0.5-tmp/(n-1)) + g2*2*max(0, tmp/(n-1)-0.5) ...
                b1*2*max(0, 0.5-tmp/(n-1)) + b2*2*max(0, tmp/(n-1)-0.5) ...
                ];
        end
    end
end


function sFileOut = out_fopen_spm(OutputFile, sFileIn, ChannelMat)
% OUT_FOPEN_SPM: Saves the header of a new empty SPM .mat/.dat file.

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2017-2022

% Initialize SPM12+CAT12
[isInstalled, errMsg] = bst_plugin('Install', 'spm12');
if ~isInstalled
    error(errMsg);
end

% Get the two output file names: .mat and .dat
[fPath, fBase, fExt] = bst_fileparts(OutputFile);
MatFile = bst_fullfile(fPath, [fBase, '.mat']);
DatFile = bst_fullfile(fPath, [fBase, '.dat']);

% Create .mat structure
D.type      = 'continuous';
D.Nsamples  = round((sFileIn.prop.times(2) - sFileIn.prop.times(1)) .* sFileIn.prop.sfreq) + 1;
D.Fsample   = sFileIn.prop.sfreq;
D.timeOnset = sFileIn.prop.times(1);
% Trials
D.trials.label  = 'Undefined';
D.trials.events = repmat(struct('type', [], 'time', [], 'value', [], 'offset', [], 'duration', []),0);
D.trials.onset  = sFileIn.prop.times(1);
D.trials.bad    = 0;
D.trials.tag    = [];
D.trials.repl   = 1;
% Events
for iEvt = 1:length(sFileIn.events)
    for iOcc = 1:size(sFileIn.events(iEvt).times,2)
        i = length(D.trials.events) + 1;
        D.trials.events(i).type = sFileIn.events(iEvt).label;
        if ~isempty(sFileIn.events(iEvt).notes) && ~isempty(sFileIn.events(iEvt).notes{iOcc})
            D.trials.events(i).type = [D.trials.events(i).type '-' sFileIn.events(iEvt).notes{iOcc}];
        end
        D.trials.events(i).time = sFileIn.events(iEvt).times(1,iOcc);
        D.trials.events(i).value = iEvt;
        D.trials.events(i).offset = 0;
        if (size(sFileIn.events(iEvt).times,1) == 2)
            D.trials.events(i).duration = sFileIn.events(iEvt).times(2,iOcc) - sFileIn.events(iEvt).times(1,iOcc);
        else
            D.trials.events(i).duration = [];
        end
    end
end
% Channels
for i = 1:length(ChannelMat.Channel)
    D.channels(i).bad      = (sFileIn.channelflag(i) == -1);
    D.channels(i).label    = ChannelMat.Channel(i).Name;
    D.channels(i).type     = ChannelMat.Channel(i).Type;
    D.channels(i).X_plot2D = [];
    D.channels(i).Y_plot2D = [];
    D.channels(i).units    = 'm';
end
% Data
D.data = file_array(DatFile, [length(ChannelMat.Channel), D.Nsamples], 'float32-le');
% File name
D.fname = [fBase, '.mat'];
D.path = fPath;

% Get sensor types
iEeg = channel_find(ChannelMat.Channel, 'EEG, SEEG, ECOG, NIRS');
iMeg = channel_find(ChannelMat.Channel, 'MEG, MEG REG');
if ~isempty(iMeg)
    error(['MEG sensors are currently not supported by this function.' 10 ...
           'Please contact us through the Brainstorm user forum to request this feature.']);
end
% If all the channels have other types: consider it's all EEG
if isempty(iEeg) && isempty(iMeg)
    iEeg = 1:length(ChannelMat.Channel);
end
% Sensors
for i = 1:length(iEeg)
    if ~isempty(ChannelMat.Channel(iEeg(i)).Loc) && ~all(ChannelMat.Channel(iEeg(i)).Loc(:) == 0)
        D.sensors.eeg.chanpos(i,:) = ChannelMat.Channel(iEeg(i)).Loc(:,1)';
        D.sensors.eeg.elecpos(i,:) = ChannelMat.Channel(iEeg(i)).Loc(:,1)';
    else
        D.sensors.eeg.chanpos(i,:) = [NaN NaN NaN];
        D.sensors.eeg.elecpos(i,:) = [NaN NaN NaN];
    end
    D.sensors.eeg.chantype{i} = lower(ChannelMat.Channel(iEeg(i)).Type);
    D.sensors.eeg.chanunit{i} = 'V';
    D.sensors.eeg.label{i}    = ChannelMat.Channel(iEeg(i)).Name;
    D.sensors.eeg.type        = 'ctf';
    D.sensors.eeg.unit        = 'm';
    D.sensors.eeg.balance.current = 'none';
end
D.sensors.chantype = 'eeg';
% Rest of the structure
D.fiducials    = repmat(struct(), 0);
D.transform.ID = 'time';
D.condlist     = {};
D.montage.M    = [];
D.montage.Mind = 0;
D.history      = repmat(struct(), 0);
D.other        = repmat(struct(), 0);

% Save file
save(MatFile, 'D');

% Create a new header structure
sFileOut = sFileIn;
sFileOut.filename  = MatFile;
sFileOut.condition = '';
sFileOut.format    = 'SPM-DAT';
sFileOut.byteorder = 'l';
sFileOut.comment   = fBase;
% Force the destination compensation level
sFileOut.prop.currCtfComp = sFileOut.prop.destCtfComp;
sFileOut.header.ctfcomp   = sFileOut.prop.destCtfComp;
% Save pointer to the SPM file in the header
sFileOut.header.file_array = D.data;
sFileOut.header.nchannels  = length(ChannelMat.Channel);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Spherical Harmonic Modeling and Analysis Toolkit (SPHARM-MAT) is a 3D
% shape modeling and analysis toolkit.
% It is a software package developed at Shenlab in Center for Neuroimaging,
% Indiana University (SpharmMat@gmail.com, http://www.iupui.edu/~shenlab/)
% It is available to the scientific community as copyright freeware
% under the terms of the GNU General Public Licence.
%
% Copyright 2009, 2010, ShenLab, Center for Neuroimaging, Indiana University
%
% This file is part of SPHARM-MAT.
%
% SPHARM-MAT 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.
%
% SPHARM-MAT 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 SPHARM-MAT. If not, see <http://www.gnu.org/licenses/>.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% ============================================
% write_meta_tri.m
%
% write *.meta file
%
% Wan Jing
% 11/08/2008 - create
%============================================
function write_meta_tri(filename, vertices, faces)

fid = fopen(filename, 'w');
fprintf(fid, 'ObjectType = Scene\n');
fprintf(fid, 'NDims = 3\n');
fprintf(fid, 'NObjects = 1\n');
fprintf(fid, 'ObjectType = Mesh\n');
fprintf(fid, 'NDims = 3\n');
fprintf(fid, 'BinaryData = False\n');
fprintf(fid, 'TransformMatrix = 1 0 0 0 1 0 0 0 1\n');
fprintf(fid, 'Offset = 0 0 0\n');
fprintf(fid, 'CenterOfRotation = 0 0 0\n');
fprintf(fid, 'ElementSpacing = 1 1 1\n');
fprintf(fid, 'PointType = MET_FLOAT\n');
fprintf(fid, 'PointDataType = MET_FLOAT\n');
fprintf(fid, 'CellDataType = MET_FLOAT\n');
fprintf(fid, 'NCellTypes = 1\n');
fprintf(fid, 'PointDim = ID x y ...\n');
fprintf(fid, 'NPoints = %d\n', length(vertices));
fprintf(fid, 'Points = \n');
for i = 0:(length(vertices)-1)
    fprintf(fid,'%d %g %g %g \n', i, vertices(i+1,1), vertices(i+1,2), vertices(i+1, 3));
end
fprintf(fid, 'CellType = TRI\n');
nfaces = 2*length(faces);
fprintf(fid, 'NCells = %d\n', nfaces);
fprintf(fid, 'Cells = \n');
for i = 0:(length(faces)-1)
    fprintf(fid, '%d %d %d %d \n', 2*i, (faces(i+1,1)-1), (faces(i+1,2)-1), (faces(i+1,3)-1));
    fprintf(fid, '%d %d %d %d \n', 2*i+1,(faces(i+1,3)-1), (faces(i+1,4)-1), (faces(i+1,1)-1));
end
fclose(fid);

return;


function p = presolveSOS(p)
if ~isempty(p.sosgroups)
    for i = 1:length(p.sosgroups)
        for j = 1:length(p.sosgroups{i})
            % Probe what happens if we activate this binary
            k = p.sosgroups{i}(j);
            other = setdiff(p.sosgroups{i},k);
            if p.ub(k) && ~any(p.lb(p.sosgroups{i}))
                % Still free to be activated
                p_ = p;
                p_.ub(other) = 0;
                p_.lb(k) = 1;
                p_ = smashFixed(p_);
                if ~isempty(fixedInfeasibleEquality(p_))
                    p.ub(k) = 0;
                end
                % Try de-activate too
                p_ = p;
                p_.ub(k) = 0;
                p_ = smashFixed(p_);
                if ~isempty(fixedInfeasibleEquality(p_))
                    p.lb(k) = 1;
                end
            end
        end
    end
    % Prune in case some are presolved completely
    for i = 1:length(p.sosgroups)
        if nnz(p.ub(p.sosgroups{i}))==1
            keep(i) = 0;
        else
            keep(i) = 1;
        end
    end
    if ~all(keep)
        p.sosgroups = {p.sosgroups{find(keep)}};
    end
end


function [ err errFrame errTot SAbsoluteT anim ] = computeError(anim, ...
  varargin)
% Compute several SFM errors
%
% It can compute:
%  - the reprojection error
%  - the 3D error between several recovered 3D shapes and a ground truth 3D
%  shape
%
% USAGE
%  [ err errFrame errTot ] = computeError( 'reproj', isProj, varargin )
%  [ err errFrame errTot SBest ] = computeError( '3D', isProj, varargin)
%
% INPUTS
%  anim       - Animation object
%  varargin   - list of paramaters in quotes alternating with their values
%       - 'doFillP', false, will replace/create anim.P with the projection
%       matrices
%       - 'animGT'   [] the ground truth anim to compare to
%       - 'doCheckAmbiguity' [true] if true, check for ambiguities due to
%       the camera model (scale for isProj and necker reversal for ~isProj)
%       - 'checkTransform' ['camera']. Transform that can be applied to get
%       a better match. Can be 'rigid', 'rigid+scale', 'homography' or
%       'camera' (cf the alignTo function)
%
% OUTPUTS 'reproj'
%  err       - [ 1 x 3 ] array of errors:
%              if no animGT is given:
%              err = mean( errFrame, 2 );
%              if an animGT is givne:
%              err(1) = mean( errFrame{1}(3,:), 2 )
%              err(2) = mean( errFrame{2}(3,:), 2 )
%              err(3) = mean( errFrame{3} )
%  errFrame  - if no animGT is given:
%              errFrame(1,:) = sum of squared reprojected distance
%                              differences per frame
%              errFrame(2,:) = sum of reprojected distances per frame
%                              divided by the span of the object
%              errFrame(3,:) = same as first one divided by the sum of
%                              squared distances
%  errFrame  - if an animGT is given, cell object:
%              {1} [ 3 x nFrame ] Bregler, CVPR00 error (SSD)
%              The rows contain the sums over the features (and for each
%              frame) of the squared errors in X-Y, in Z, and in 3D.
%              I.e. the rows contain for each frame:
%                sum_{i=1}^nPoint  (xReconstructed_i-xOriginal_i)^2
%                                         +(yReconstructed_i-yOriginal_i)^2
%                sum_{i=1}^nPoint  (zReconstructed_i-zOriginal_i)^2
%                row1+row2
%              {2} [ 3 x nFrame ] Torresani, Bregler, CVPR01 error
%              Same as above but distances are considered (not squared
%              distances), average and not sum, and the results are
%              normalized by the span of the object
%              {3} [ 1 x nFrame ] Torresani, PAMI08 error
%              At each frame:
%              \Vert SReconstructed-SOriginal \Vert_F/
%                               \Vert SOriginal \Vert_F
%  errTot   - [3 x nPoint x nFrame] error for each point at each frame
%             as a distance (simply abs of diff, not squared)
%  SAbsoluteT - the best transformed S to match SGTAbsolute (SGT
%               transformed by its R and t)
%  anim     - the original Animation object but modified is aksed to be
%
% EXAMPLE
%
% See also Animation
%
% Vincent's Structure From Motion Toolbox      Version 3.1.1
% Copyright (C) 2008-2011 Vincent Rabaud.  [vrabaud-at-cs.ucsd.edu]
% Please email me if you find bugs, or have suggestions or questions!
% Licensed under the GPL [see external/gpl.txt]

global IS_EXIST_PDIST;
if isempty(IS_EXIST_PDIST); IS_EXIST_PDIST=exist('pdist','file'); end

[ doFillP animGT doCheckAmbiguity checkTransform ] = ...
  getPrmDflt( varargin, ...
  { 'doFillP' false 'animGT' [] ...
  'doCheckAmbiguity' true 'checkTransform' 'camera'}, 1 );

nFrame = anim.nFrame; nPoint = anim.nPoint;
method='reproj';
if ~isempty(animGT); method = '3D'; end

switch method
  case 'reproj'
    % compute the projected points
    [ W disc anim ]=generateW(anim,'doFillP', doFillP);

    % precompute stuff for error number 2
    spanW = zeros(1,nFrame);
    WIsnan=isnan(W);
    hasAnyNan=any(WIsnan(:));
    if hasAnyNan
      if IS_EXIST_PDIST
        for i = 1 : nFrame
          spanW(i) = max(pdist( anim.W(:,~WIsnan(1,:,i),i)));
        end
      else
        for i = 1 : nFrame
          tmp=anim.W(:,~WIsnan(1,:,i),i);
          spanW(i) = max(max(pdist2( tmp, tmp)));
        end
      end
    else
      if IS_EXIST_PDIST
        for i = 1 : nFrame
          spanW(i) = max(pdist( anim.W(:,:,i)));
        end
      else
        for i = 1 : nFrame
          spanW(i) = max(max(pdist2( anim.W(:,:,i), anim.W(:,:,i))));
        end
      end
    end

    % compute the different errors
    errFrame = zeros( 3, nFrame );
    tmp = W - anim.W;

    % if there is a mask, only keep certain points on certain frames
    WNorm = W;
    if ~isempty(anim.mask)
      % set to 0 the ones that are 0 in the mask
      mask = repmat(reshape(~anim.mask,1,nPoint,nFrame), [2, 1, 1]);
      tmp(mask) = 0;
      WNorm(mask(:)) = 0;
    end
    WNorm = sum(reshape(WNorm,[],nFrame).^2, 1);

    errTot = abs(tmp);
    errFrame(1,:) = sum(reshape(tmp,[],nFrame).^2,1);
    errFrame(2,:) = sum(reshape(errTot,[],nFrame),1)./spanW;
    errFrame(3,:) = sqrt(errFrame(1,:)./WNorm);

    err = mean( errFrame, 2 );
  case '3D'
    [ disc disc disc disc SAbsoluteT SGTAbsolute ] = alignTo(anim, ...
      animGT, checkTransform);

    SDiff=SAbsoluteT-SGTAbsolute;

    % Update the errors
    errTot=SDiff;

    errFrame=cell(1,3);
    % compute the first type of error
    %            {1} [ 3 x nFrame ] Bregler, CVPR00 error (SSD)
    %            The rows contain the sums over the features (and for each
    %            frame) of the squared errors in X-Y, in Z, and in 3D.
    %            I.e. the rows contain for each frame:
    %              sum_{i=1}^nPoint  (xReconstructed_i-xOriginal_i)^2
    %                                     +(yReconstructed_i-yOriginal_i)^2
    %              sum_{i=1}^nPoint  (zReconstructed_i-zOriginal_i)^2
    %                row1+row2
    errFrame{1} = zeros( 3, nFrame );
    errFrame{1}(1,:) = reshape(sum(sum( SDiff(1:2,:,:).^2, 2 ),1),1,[]);
    errFrame{1}(2,:) = reshape(sum(SDiff(3,:,:).^2, 2 ),1,[]);
    errFrame{1}(3,:) = errFrame{1}(1,:)+errFrame{1}(2,:);

    %       {2} [ 3 x nFrame ] Torresani, Bregler, CVPR01 error
    %       Same as above but distances are considered (not squared
    %       distances), average and not sum, and the results are normalized
    %       by the span of the object
    normS = sqrt(reshape(sum(sum(animGT.S.^2,1),2),1,[]));
    errFrame{2} = zeros( 3, nFrame );
    errFrame{2}(1,:) = mean(sqrt(reshape(sum(sum( SDiff(1:2,:,:).^2, ...
      2),1),1,[])));
    errFrame{2}(2,:) = mean(reshape(SDiff(3,:,:),1,[]));
    errFrame{2}(3,:) = mean(sqrt(reshape(sum(sum( SDiff(:,:,:).^2, ...
      2),1),1,[])));
    % precompute stuff for error number 2
    spanS = zeros(1,size(animGT.S,3));
    if exist('pdist','file')
      for i = 1 : size(animGT.S,3)
        spanS(i) = max(pdist( animGT.S(:,:,i)));
      end
    else
      for i = 1 : size(animGT.S,3)
        spanS(i) = max(max(pdist2( animGT.S(:,:,i), animGT.S(:,:,i))));
      end
    end
    if size(spanS,2)==1; spanS = repmat(spanS,1,nFrame); end
    errFrame{2} = bsxfun(@rdivide, errFrame{2}, spanS);

    %              {3} [ 1 x nFrame ] Torresani, PAMI08 error
    %              At each frame:
    %              \Vert SReconstructed-SOriginal \Vert_F/
    %                               \Vert SOriginal \Vert_F
    errFrame{3} = sqrt(errFrame{1}(3,i))./normS;

    % Update other errors
    err=[ mean(errFrame{1}(3,:)) mean(errFrame{2}(3,:)) ...
      mean(errFrame{3}) ];
end


function [names,data,data_endo,data_endo_a,data_endo_c,data_endo_c_lags,data_exo,data_exo_a,data_exo_p,data_exo_c,data_exo_c_lags,Fperiods,Fcomp,Fcperiods,Fcenddate,ar,priorexo,lambda4_2,favar]=...
    gensample(startdate,enddate,VARtype,Fstartdate,Fenddate,Fendsmpl,endo,exo,frequency,lags,F,CF,ar,lambda4,PriorExcel,priorsexogenous,pref,favar,IRFt, numendo)

% Phase 1: data loading and error checking

% if we have a FAVAR: read information data, data transformation, create indices, compute factors (PC)
if favar.FAVAR==1
    [informationstartlocation,informationendlocation,favar]=bear.favar_gensample1(startdate,enddate,favar,pref);
end

% first read the data from Excel
[data,names]=xlsread(pref.excelFile,'data');

% now, as a preliminary step: check if there is any Nan in the data; if yes, return an error since the model won't be able to run with missing data
% a simple way to test for NaN is to check for "smaller or equal to infinity": Nan is the only number for which matlab will return 'false' when asked so
%[r,c]=size(data);

% identify the date strings
datestrings=names(2:end,1);
% identify the position of the string corresponding to the start period
startlocationData=find(strcmp(datestrings,startdate));
% identify the position of the string corresponding to the end period
endlocation=find(strcmp(datestrings,enddate));
startlocation=startlocationData;
if favar.FAVAR==1 % in case we transform the data to first or second differences, we have a different startlocation
    if favar.transformation==1
        startlocation=informationstartlocation;
        endlocation=informationendlocation;
        % check is correct
    else
        startlocation=startlocationData;
    end
end

% save the whole sample temporarily
data1=data;
% adjust data to startdate and enddate
% data1=data(startlocationData:endlocation,:);
data=data(startlocationData:endlocation,:);

% identify the variable strings, endogenous and exogenous
variablestrings=names(1,2:end);

% FAVAR: augment data and variablestrings with factors
if favar.FAVAR==1
    [data,variablestrings,favar]=bear.favar_gensample2(data1,endo,variablestrings,startlocation,lags,favar);
end

% if either the start date or the date date is not recognised, return an error message
if isempty(startlocation)
    error('bear:BEARmain:UnknownStartDate', ...
        'Error: unknown start date for the sample. Please check your sample start date (remember that names are case-sensitive).');
elseif isempty(endlocation)
    error('bear:BEARmain:UnknownEndDate', ...
        'Error: unknown end date for the sample. Please check your sample end date (remember that names are case-sensitive).');
end
% also, if the start date is posterior to the end date, obviously return an error
if startlocation>=endlocation==1
    error('bear:BEARmain:InconsistentStartEndDates', ...
        'Error: inconsistency between the start and end dates. The start date must be anterior to the end date.');
end

% identify the position of the strings corresponding to the endogenous variables
% for each variable, find the corresponding string
for ii=1:numendo
    % check first that the variable ii in endo appears in the list of variable strings
    % if not, the variable is unknown: return an error
    var=endo{ii,1};
    check=find(strcmp(variablestrings,var));
    if isempty(check)==1
        message=['Error: endogenous variable ' var ' cannot be found on the excel data spreadsheet.'];
        error('BEARmain:gensample:EndoVarNotFound', message);
    end
    % if the variable is known, go on
    endolocation(ii,1)=find(strcmp(variablestrings,endo(ii,1)));
end

% identify the position of the strings corresponding to the exogenous variables
% proceed similarly to the endogenous variables, but account for the fact that exogenous may be empty
% so check first whether there are exogenous variables altogether
if isempty(exo)
    numexo=0;
else
    % if not empty, repeat what has been done with the exogenous
    numexo=size(exo,1);
    % for each variable, find the corresponding string
    for ii=1:numexo
        % check first that the variable ii in endo appears in the list of variable strings
        % if not, the variable is unknown: return an error
        var=exo{ii,1};
        check=find(strcmp(variablestrings,var));
        if isempty(check)==1
            message=['Error: exogenous variable ' var ' cannot be found on the excel data spreadsheet.'];
            error('BEARmain:gensample:ExoVarNotFound', message);
        end
        % if the variable is known, go on
        exolocation(ii,1)=find(strcmp(variablestrings,exo(ii,1)));
    end
end

% Phase 2: creation of the data matrices data_endo and data_exo

% now create the matrix of endogenous variables for the estimation sample
% it is simply the concatenation of the vectors of each endogenous variables, over the selected sample dates
data_endo=[];
% loop over endogenous variables
for ii=1:numendo
    data_endo=[data_endo data(:,endolocation(ii,1))];
end

% Similarly, create the matrix of exogenous variables for the estimation sample
data_exo=[];
for ii=1:numexo
%     data_exo=[data_exo data(startlocation:endlocation,exolocation(ii,1))];
    data_exo=[data_exo data(:,exolocation(ii,1))];
end

%% FAVAR

if favar.FAVAR==1
    if favar.transformation==1
        % correct order of transformationindex_endo
        favar.transformationindex_endo=[];
        for ii=1:size(endo,1)
            favar.transformationindex_endo=[favar.transformationindex_endo favar.transformationindex_endo_temp(:,endolocation(ii,1))];
        end
    end
    % correct order of stddev
    favar.data_exfactors_stddev=[];
    for ii=1:size(endo,1)
        favar.data_exfactors_stddev=[favar.data_exfactors_stddev favar.data_exfactors_stddev_temp(:,endolocation(ii,1))];
    end

    % compute new loadings
    %favar.L=(mvregress(data_endo,favar.X,'algorithm','cwls'))'; %appears to be not feasible for very large X
    %favar.L=(olssvd(favar.X,data_endo))';

    % IRF shock to plot
    if favar.IRFplot==1
        favar.IRF.npltXshck=size(favar.IRF.pltXshck,1);
        if IRFt==1||IRFt==2||IRFt==3
            plotXshock_indexlogical=ismember(endo,favar.IRF.pltXshck);
            favar.IRF.plotXshock_index=find(plotXshock_indexlogical==1)';
            if favar.IRF.npltXshck==0
                % error if no shock to plot is found, otherwise code crashes at a later stage
                message=['Error: Shock(' favar.IRF.npltXshck ') cannot be found.'];
                error('BEARmain:gensample:favar_IRF_npltXshck_error',message);
            end
        end
        % for IRFt 4 & 6 this step is done in loadsignres
    end

    % rotate Factors, compute new loadings for onestep, twostep estimation
    [data_endo,favar]=bear.favar_gensample3(data_endo,favar);

%%%% stationarity test
endostrings=endo(favar.variablestrings_exfactors,1);
allstrings=[favar.informationvariablestrings,endostrings'];
NSTindex=[];
NST2index=[];
catallstrings=[];
catallstrings2=[];
count=0;
count2=0;
for ii=1:size(favar.XY,2)
    adf(ii,1)=adftest(favar.XY(:,ii));
    kpss(ii,1)=kpsstest(favar.XY(:,ii));
    if adf(ii,1)==0 %|| kpss(ii,1)==1
        count=count+1;
        NSTallstrings{count,1}=allstrings(1,ii);
        NSTindex=[NSTindex;ii];
        catallstrings=strcat(catallstrings,', ',NSTallstrings{count,1});
    end
    if kpss(ii,1)==1
        count2=count2+1;
        NST2allstrings{count2,1}=allstrings(1,ii);
        NST2index=[NST2index;ii];
        catallstrings2=strcat(catallstrings2,', ',NST2allstrings{count2,1});
    end
end
if size(NSTindex,1) > 0
fprintf('%d%s%s%s\n',size(NSTindex,1),' series in X are not stationary (ADF test): ',catallstrings{1,1},'.');
end
if size(NST2index,1) > 0
fprintf('%d%s%s%s\n',size(NST2index,1),' series in X are not stationary (KPSS test): ',catallstrings2{1,1},'.');
end
%%%%

end

%% Bens check for missing values in exo and endo
[t,g]=size(data_endo);
for ii=1:t
    for jj=1:g
        temp=data_endo(ii,jj);
        if (temp<=inf)==0
            % identify the variable and the date %%%%% why is this commented
            %       NaNvariable=names{1,jj+1};
            %       NaNdate=names{ii+1,1};
            %       message=['Error: variable ' NaNvariable ' at date ' NaNdate ' (and possibly other sample entries) is identified as NaN. Please check your Excel spreadsheet: entry may be blank or non-numerical.'];
            %       msgbox(message);
            error('programme termination: An endogenous variable entry is missing');
        end
    end
end
%
%
[t,g]=size(data_exo);
for ii=1:t
    for jj=1:g
        temp=data_exo(ii,jj);
        if (temp<=inf)==0
            error('programme termination: An exogenous variable entry is missing');
        end
    end
end

% Prior values settings for the AR coefficients (either default same for all or individually in Excel in AR prior sheet)
if PriorExcel==0
    ar_default=NaN(numendo,1);
    ar_default(:,1)=ar;
    ar=ar_default;
else
    [ar]=xlsread(pref.excelFile,'AR priors');
end

if priorsexogenous==0
    exo_default=zeros(numendo,numexo+1);
    lambda4_default=zeros(numendo,numexo+1);
    for ii=1:numendo
        for jj=1:numexo+1
            priorexo(ii,jj)=0;
            lambda4_2(ii,jj)=lambda4;
        end
    end
else
    [priorexo]=xlsread(pref.excelFile,'exo mean priors');
    [lambda4]=xlsread(pref.excelFile,'exo tight priors');
    priorexo=priorexo(1:numendo,1:numexo+1);
    lambda4_2=lambda4(1:numendo,1:numexo+1);
end

% Phase 3: determination of the position of the forecast start and end periods

% if both unconditional and conditional forecasts were not selected, there is no need for all the forecast-specific matrices: simply return empty matrices
if (VARtype==1 && F==0) || ((VARtype==2 || VARtype==3 || VARtype==5 || VARtype==6 ) && (F==0 && CF==0))
    data_endo_a=[];
    data_exo_a=[];
    data_exo_p=[];
    Fperiods=[];
    Fcomp=[];
    Fcperiods=[];
    data_endo_c=[];
    data_endo_c_lags=[];
    data_exo_c=[];
    data_exo_c_lags=[];
    Fcenddate=[];

    % if forecast were selected, create all the required elements
else

    % preliminary tasks
    % first, identify the date strings, and the variable strings
    datestrings=names(2:end,1);
    if favar.FAVAR==1 % in case we transform the data to first or second differences, we have a different datestrings
        if favar.transformation==1
            datestrings=names(1+favar.informationstartlocation:end,:); %first row are labels
        end
    end

    % identify the location of the last period in the dataset
    dataendlocation=size(datestrings,1);

    % identify the position of the start period (the easy part)
    % if the start period has been selected as the first period after the sample end, identifies it directly
    if Fendsmpl==1
        Fstartlocation=find(strcmp(datestrings,enddate))+1;
        % if the start period has not been selected as the first period after the sample end, it must be within the sample: look for it
    elseif Fendsmpl==0
        Fstartlocation=find(strcmp(datestrings,Fstartdate));
        % if the start date is not recognised, return an error message
        if isempty(Fstartlocation)==1
            msgbox('Error: unknown start date for the forecasts. Select a date within a sample, or select "Start forecasts after last sample period"');
            error('unknown start date for the forecasts');
        end
    end

    % identify the position of the final forecast period (the hard part)
    % this period can be in or outside the sample, depending on the user's choice

    % if the data is yearly
    if frequency==1
        Fendlocation=str2num(Fenddate(1,1:end-1))-str2num(datestrings{1,1}(1,1:end-1))+1;

        % if the data is quarterly
    elseif frequency==2
        % first identify the year and quarter of the initial date in the whole data set (not just the sample)
        datastartyear=str2num(datestrings{1,1}(1,1:4));
        datastartquarter=str2num(datestrings{1,1}(1,6));
        % convert this date into quarters only
        datastart=datastartyear*4+datastartquarter;
        % then identify the year and quarter of the final forecast date
        forecastendyear=str2num(Fenddate(1,1:4));
        forecastendquarter=str2num(Fenddate(1,6));
        % convert this date into quarters only
        forecastend=forecastendyear*4+forecastendquarter;
        % finally, compute the number of periods that separate the two dates
        Fendlocation=forecastend-datastart+1;

        % if the data is monthly
    elseif frequency==3
        % first identify the year and month of the initial date in the whole data set (not just the sample)
        temp=datestrings{1,1};
        datastartyear=str2num(temp(1,1:4));
        temp(1,5)=' ';
        [~,datastartmonth]=strtok(temp);
        % convert this date into months only
        datastart=datastartyear*12+str2num(datastartmonth);
        % then identify the year and month of the final forecast date
        temp=Fenddate;
        forecastendyear=str2num(temp(1,1:4));
        temp(1,5)=' ';
        [~,forecastendmonth]=strtok(temp);
        % convert this date into months only
        forecastend=forecastendyear*12+str2num(forecastendmonth);
        Fendlocation=forecastend-datastart+1;

        % if the data is weekly
    elseif frequency==4
        % then identify the year and week corresponding to this final period
        temp=datestrings{end,1};
        dataendyear=str2num(temp(1,1:4));
        temp(1,5)=' ';
        [~,dataendweek]=strtok(temp);
        dataendweek=str2num(dataendweek);
        % identify the year and week corresponding to the end of the forecast period
        temp=Fenddate;
        Fendyear=str2num(temp(1,1:4));
        temp(1,5)=' ';
        [~,Fendweek]=strtok(temp);
        Fendweek=str2num(Fendweek);
        % determine whether the forecast period ends within the data set, or after the end of the data set
        if Fendyear<dataendyear
            insmpl=1;
        elseif Fendyear==dataendyear && Fendweek<=dataendweek
            insmpl=1;
        else
            insmpl=0;
        end
        % if the forecast end period lies within the dataset, simply detect its location
        if insmpl==1
            Fendlocation=find(strcmp(Fenddate,datestrings));
            % if it is outside the data set, complete until the forecast end date is reached (assuming 52 weeks per year)
        elseif insmpl==0
            % Consider two cases separately
            % first case: if the end year of the forecast is the same as the end year of the dataset
            % in this case, simply complete the missing periods
            if dataendyear==Fendyear
                complement=Fendweek-dataendweek;
                Fendlocation=dataendlocation+complement;
                % if the end year of the forecasts is posterior to the last year of the data
            elseif dataendyear<Fendyear
                % complete the first year (the one shared that ends the data set)
                complement=52-dataendweek;
                % complete the following years before the last one (if any)
                for ii=dataendyear+1:Fendyear-1
                    complement=complement+52;
                end
                % complete the final forecast year
                complement=complement+Fendweek;
                Fendlocation=dataendlocation+complement;
            end
        end

        % if the data is daily
    elseif frequency==5
        % then identify the year and day corresponding to this final period
        temp=datestrings{end,1};
        dataendyear=str2num(temp(1,1:4));
        temp(1,5)=' ';
        [~,dataendday]=strtok(temp);
        dataendday=str2num(dataendday);
        % identify the year and day corresponding to the end of the forecast period
        temp=Fenddate;
        Fendyear=str2num(temp(1,1:4));
        temp(1,5)=' ';
        [~,Fendday]=strtok(temp);
        Fendday=str2num(Fendday);
        % determine whether the forecast period ends within the data set, or after the end of the data set
        if Fendyear<dataendyear
            insmpl=1;
        elseif Fendyear==dataendyear && Fendday<=dataendday
            insmpl=1;
        else
            insmpl=0;
        end
        % if the forecast end period lies within the dataset, simply detect its location
        if insmpl==1
            Fendlocation=find(strcmp(Fenddate,datestrings));
            % if it is outside the data set, complete until the forecast end date is reached (assuming 261 opening days per year)
        elseif insmpl==0
            % Consider two cases separately
            % first case: if the end year of the forecast is the same as the end year of the dataset
            % in this case, simply complete the missing periods
            if dataendyear==Fendyear
                complement=Fendday-dataendday;
                Fendlocation=dataendlocation+complement;
                % if the end year of the forecasts is posterior to the last year of the data
            elseif dataendyear<Fendyear
                % complete the first year (the one shared that ends the data set)
                complement=261-dataendday;
                % complete the following years before the last one (if any)
                for ii=dataendyear+1:Fendyear-1
                    complement=complement+261;
                end
                % complete the final forecast year
                complement=complement+Fendday;
                Fendlocation=dataendlocation+complement;
            end
        end

        % finally, if the data is undated
    elseif frequency==6
        Fendlocation=str2num(Fenddate(1,1:end-1))-str2num(datestrings{1,1}(1,1:end-1))+1;
    end

    % from this, conclude the total number of forecast periods
    Fperiods=Fendlocation-Fstartlocation+1;

    if Fperiods<0
        msgbox('Error: The forecast start date needs to be prior to the forecast end date');
        error('invalid forecast start or end date');
    end

    % Phase 4: generation of the forecast-specific matrices
    if favar.FAVAR==0
        % load the full sample in this case
        data=data1;
    elseif favar.FAVAR==1
        data=favar.data_full;
    end
    % now create the matrix of endogenous variables for the pre-forecast period
    % it is simply the concatenation of the vectors of each endogenous variables, over the selected sample dates
    data_endo_a=[];
    % loop over endogenous variables
    for ii=1:numendo
        data_endo_a=[data_endo_a data(1:Fstartlocation-1,endolocation(ii,1))];
    end
    % also, create the matrix of exogenous variables for the pre-forecast period
    data_exo_a=[];
    for ii=1:numexo
        data_exo_a=[data_exo_a data(1:Fstartlocation-1,exolocation(ii,1))];
    end

    % create the matrix of endogenous variables for the period common to actual data and forecasts (for forecast evaluation)
    % first, check that there are such common periods: it is the case if the beginning of the forecast period is anterior to the end of the dataset
    if Fstartlocation<=dataendlocation
        % return a scalar value to indicate that forecast evaluation is possible
        Fcomp=1;
        % compute the number of common periods
        % if the forecast period ends before the end of the data set, the common periods end with the end of the forecasts
        if Fendlocation<=dataendlocation
            Fcperiods=Fperiods;
            % record the end date of the common periods
            Fcenddate=Fenddate;
            % if the forecast period ends later than the data set, the common periods end at the end of the data set
        elseif Fendlocation>dataendlocation
            Fcperiods=dataendlocation-Fstartlocation+1;
            % record the end date of the common periods
            Fcenddate=datestrings{end,1};
        end

        % create a matrix of endogenous data for the common periods
        data_endo_c=[];
        for ii=1:numendo
            data_endo_c=[data_endo_c data(Fstartlocation:min(dataendlocation,Fendlocation),endolocation(ii,1))];
        end

        % create a lagged matrix of endogenous data prior to the common periods
        % the number of values is equal to "lags"; this will be used for computation of the log predictive score
        data_endo_c_lags=[];
        for ii=1:numendo
            data_endo_c_lags=[data_endo_c_lags data(Fstartlocation-lags:Fstartlocation-1,endolocation(ii,1))];
        end

        % create a matrix of exogenous data for the common periods
        data_exo_c=[];
        for ii=1:numexo
            data_exo_c=[data_exo_c data(Fstartlocation:min(dataendlocation,Fendlocation),exolocation(ii,1))];
        end

        % create a lagged matrix of exogenous data prior to the common periods
        % the number of values is equal to "lags"; this will be used for computation of the log predictive score
        data_exo_c_lags=[];
        for ii=1:numexo
            data_exo_c_lags=[data_exo_c_lags data(Fstartlocation-lags:Fstartlocation-1,exolocation(ii,1))];
        end
        % if there are no common periods, return a scalar value to indicate that forecast evaluation is not possible
    else
        Fcomp=0;
        Fcperiods=0;
        data_exo_c=[];
        data_endo_c=[];
        Fcenddate=[];
        data_endo_c_lags=[];
        data_exo_c_lags=[];
    end

    % now create the matrix data_exo_p
    % two possible cases

    % if there are no exogenous variables, simply create an empty matrix
    if isempty(exo)
        data_exo_p=[];

        % if there are exogenous variables, load from excel
    else
        % load the data from Excel
        [num txt strngs]=xlsread(pref.excelFile,'pred exo');

        % obtain the row location of the forecast start date
        [Fstartlocation,~]=find(strcmp(strngs,Fstartdate));
        % check that the start date for the forecast appears in the sheet; if not, return an error
        if isempty(Fstartlocation)
            message=['Error: a forecast application is selected for a model that uses exogenous variables. Hence, predicted exogenous values should be supplied over the forecast periods. Yet the start date for forecasts (' Fstartdate ') cannot be found on the ''pred exo'' sheet of the Excel data file. Please verify that this sheet is properly filled, and remember that dates are case-sensitive.'];
            msgbox(message);
            error('programme termination: data error');
        end
        % obtain the row location of the forecast end date
        [Fendlocation,~]=find(strcmp(strngs,Fenddate));
        % check that the end date for the forecast appears in the sheet; if not, return an error
        if isempty(Fendlocation)
            message=['Error: a forecast application is selected for a model that uses exogenous variables. Hence, predicted exogenous values should be supplied over the forecast periods. Yet the end date for forecasts (' Fenddate ') cannot be found on the ''pred exo'' sheet of the Excel data file. Please verify that this sheet is properly filled, and remember that dates are case-sensitive.'];
            msgbox(message);
            error('programme termination: data error');
        end

        % identify the strings for the exogenous variables
        % loop over exogenous
        for ii=1:numexo
            % try to find a column match for exogenous variable ii
            [~,location]=find(strcmp(strngs,exo{ii,1}));
            % if no match is found, return an error
            if isempty(location)
                message=['Error: a forecast application is selected for a model that uses exogenous variables. Hence, predicted exogenous values should be supplied over the forecast periods. Yet the exogenous variable ''' exo{ii,1} ''' cannot be found on the ''pred exo'' sheet of the Excel data file. Please verify that this sheet is properly filled, and remember that variable names are case-sensitive.'];
                msgbox(message);
                error('programme termination: data error');
                % else, record the value
            else
                pexolocation(ii,1)=location;
            end
        end

        % if everything was fine, reconstitute the matrix data_exo_p
        % initiate
        data_exo_p=[];
        % loop over exogenous variables
        for ii=1:numexo
            % initiate the predicted values for exogenous variable ii
            predexo=[];
            % loop over forecast periods
            for jj=1:Fperiods
                temp=strngs{Fstartlocation+jj-1,pexolocation(ii,1)};
                % if this entry is empty or NaN, return an error
                if (isempty(temp) || (temp<=inf)==0)
                    message=['Error: the predicted value for exogenous variable ' exo{ii,1} ' at forecast period ' strngs{Fstartlocation+jj,1} ' (and possibly other entries) is either empty or NaN. Please verify that the ''pred exo'' sheet of the Excel data file is properly filled.'];
                    msgbox(message);
                    error('programme termination: data error');
                    % if this entry is a number, record it
                else
                    predexo=[predexo;temp];
                end
            end
            % concatenate
            data_exo_p=[data_exo_p predexo];
        end

        % also, record the exogenous values on Excel
        % replace NaN entries by blanks
        strngs(cellfun(@(x) any(isnan(x)),strngs))={[]};
        % then save on Excel
        if pref.results==1
            bear.xlswritegeneral(fullfile(pref.results_path, [pref.results_sub '.xlsx']),strngs,'pred exo','A1');
        end
    end

end