classdef CounterFlowDiffusionFlame < ct.oneD.Sim1D
    % Create a counter flow diffusion flame stack. ::
    %
    %     >> flame = ct.oneD.CounterFlowDiffusionFlame(left, flow, right, oxidizer)
    %
    % :param left:
    %     :mat:class:`ct.oneD.Inlet` object representing the left ('fuel') inlet boundary.
    % :param flow:
    %     :mat:class:`ct.oneD.AxisymmetricFlow` object representing the flow domain.
    % :param right:
    %     :mat:class:`ct.oneD.Inlet` object representing the right ('oxidizer') inlet
    %     boundary.
    % :param oxidizer:
    %     String representing the oxidizer species. Most commonly O2.

    methods

        function obj = CounterFlowDiffusionFlame(left, flow, right, oxidizer)

            arguments
                left(1, 1) ct.oneD.Inlet
                flow(1, 1) ct.oneD.Flow
                right(1, 1) ct.oneD.Inlet
                oxidizer(1, :) char = 'O2'
            end

            %% Define elMoles function

            function moles = elMoles(tp, element)
                % Determine the elemental moles in a gas object per unit mass.

                % Check input parameters
                if nargin ~= 2
                    error('elMoles expects two input arguments.');
                end

                if ~tp.isIdealGas
                    error('Gas object must represent an ideal gas mixture.');
                end

                if ~ischar(element)
                    error('Element name must be of format character.');
                end

                % Calculate the moles per mass of mixture of an element within a gas
                % object. The equation used is:
                %
                %    elMoles = elMassFrac/Mel
                %
                % where elMassFrac is the elemental mass fraction within the gas object
                % using the elementalMassFraction function; Mel is the atomic mass of
                % the element.

                elMassFrac = tp.elementalMassFraction(element);
                eli = tp.elementIndex(element);
                M = tp.atomicWeights;
                Mel = M(eli);
                moles = elMassFrac / Mel;

            end

            %% Get properties of fuel and oxidizer streams.
            % To be used in determining velocity of each stream. Also get the
            % temperature of both inlet streams.

            % Oxidizer stream
            right.updateState()
            tp_o = right.phase;
            rho0 = tp_o.massDensity;
            tox = right.T;
            yox = tp_o.Y;
            sOx = elMoles(tp_o, 'O') - 2 * elMoles(tp_o, 'C') - ...
                    0.5 * elMoles(tp_o, 'H');

            ioxidizer = tp_o.speciesIndex(oxidizer);

            % Fuel stream
            left.updateState()
            tp_f = left.phase;
            rhof = tp_f.massDensity;
            tf = left.T;
            yf = tp_f.Y;
            sFuel = elMoles(tp_f, 'O') - 2 * elMoles(tp_f, 'C') - ...
                    0.5 * elMoles(tp_f, 'H');

            spec = tp_f.speciesNames;  % Get all of the species names in gas object.
            nsp = tp_f.nSpecies;  % Get total number of species in gas object.

            %% Calculate the stoichiometric mixture fraction.
            % Needed for determining location of flame edges and composition.
            % elMoles function used to calculate the number of moles of C, H, and O
            % atoms in the fuel and oxidizer streams:
            %
            %    elMoles = elementalMassFraction/element atomic weight.
            %
            % From this, the stoichiometric Air/Fuel ratio can be determined.
            % 1 Mole of O needs 2 Moles of C and 0.5 Moles of H for stoichiometric
            % conditions. The stoichiometric mixture fraction, Zst, is then
            % calculated.

            phi = sFuel / sOx;
            zst = 1.0 / (1.0 - phi);

            %% Compute the stoichiometric mass fractions of each species.
            % Use this to set the fuel gas object and calculate adiabatic flame
            % temperature and equilibrium composition.

            ystoich_double = zeros(1, nsp); % Create empty vector for stoich mass frac.

            for n = 1:nsp
                % Calculate stoichiometric mass fractions.
                ystoich_double(n) = zst * yf(n) + (1.0 - zst) * yox(n);
                % Convert mass fraction vector to string vector.
                ystoich_str{n} = num2str(ystoich_double(n));
                % Convert string vector to cell with SPECIES:MASS FRACTION format.
                y_stoich{n} = [spec{n}, ':', ystoich_str{n}];
            end

            % Initialize stoichiometric mass fraction cell with first SP:Y value.
            ystoich = [y_stoich{1}];

            for i = 2:nsp
                % Update cell to have format similar to N2:Yst,O2:Yst,...
                ystoich = [ystoich ',', y_stoich{i}];
            end

            % Set the fuel gas object as stoichiometric values and use equilibrate
            % function to determine stoichiometric equilibrium temperature and mass
            % fractions.
            tp_f.TPY = {tf, tp_f.P, ystoich};
            tp_f.equilibrate('HP');
            teq = tp_f.T;
            yeq = tp_f.Y;

            %% Estimate the strain rate.
            % Based on the inlet stream velocities and determine initial 'guess'
            % for mixture fraction based on mass flux ratio.

            zz = flow.grid;
            dz = zz(end) - zz(1);
            mdotl = left.massFlux;
            mdotr = right.massFlux;
            uleft = mdotl / rhof;
            uright = mdotr / rho0;
            a = (abs(uleft) + abs(uright)) / dz;
            diff = tp_f.mixDiffCoeffs;
            f = sqrt(a / (2.0 * diff(ioxidizer)));
            x0num = sqrt(uleft * mdotl) * dz;
            x0den = sqrt(uleft * mdotr) + sqrt(uright * mdotr);
            x0 = x0num / x0den;

            %% Calculate initial values of temperature and mass fractions.
            % These values to be used for energy equation solution. Method is based
            % on the Burke-Schumann model.

            nz = flow.nPoints;
            zm = zeros(1, nz);
            u = zeros(1, nz);
            v = zeros(1, nz);
            y = zeros(nz, nsp);
            t = zeros(1, nz);

            for j = 1:nz
                x = zz(j);
                zeta = f * (x - x0);
                zmix = 0.5 * (1.0 - erf(zeta)); % Mixture fraction in flame.
                zm(j) = zmix;
                u(j) = a * (x0 - zz(j)); % Axial velocity.
                v(j) = a; % Radial velocity.

                if zmix > zst

                    for n = 1:nsp
                        y(j, n) = yeq(n) + (zmix - zst) * (yf(n) - yeq(n)) /...
                                 (1.0 - zst);
                    end

                    t(j) = teq + (tf - teq) * (zmix - zst) / (1.0 - zst);
                else

                    for n = 1:nsp
                        y(j, n) = yox(n) + zmix * (yeq(n) - yox(n)) / zst;
                    end

                    t(j) = tox + zmix * (teq - tox) / zst;
                end

            end

            zrel = zz / dz;

            %% Create the flame stack.
            % Set the profile of the flame with the estimated axial velocities,
            % radial velocities, temperature, and mass fractions calculated above.
            obj@ct.oneD.Sim1D({left flow right});
            flow.setProfile('velocity', zrel, u);
            flow.setProfile('spreadRate', zrel, v);
            flow.setProfile('T', zrel, t);

            for n = 1:nsp
                nm = tp_f.speciesName(n);
                flow.setProfile(nm{:}, zrel, transpose(y(:, n)));
            end

        end

    end

end
