# -*- coding: utf-8 -*-
'''Chemical Engineering Design Library (ChEDL). Utilities for process modeling.
Copyright (C) 2019, 2020 Caleb Bell <Caleb.Andrew.Bell@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.'''

__all__ = ['FlashVLN']

from .flash_vl import FlashVL
from chemicals.rachford_rice import flash_inner_loop
from chemicals.exceptions import (
    TrivialSolutionError,
    PhaseCountReducedError, 
    PhaseExistenceImpossible
)
from fluids.numerics import (
    UnconvergedError,
    OscillationError,
    NoSolutionError
)
from thermo.phase_identification import identify_sort_phases
from thermo.property_package import StabilityTester
from thermo.bulk import default_settings
from .flash_utils import (
    sequential_substitution_NP,
    sequential_substitution_2P,
    one_in_list,
    empty_flash_conv,
    deduplicate_stab_results
)

CAS_H2O = '7732-18-5'

class FlashVLN(FlashVL):
    r'''Class for performing flash calculations on multiphase vapor-liquid
    systems. This rigorous class does not make any assumptions and will search
    for up to the maximum amount of liquid phases specified by the user. Vapor
    and each liquid phase do not need to use a consistent thermodynamic model.

    The minimum information that is needed in addition to the :obj:`Phase`
    objects is:

    * MWs
    * Vapor pressure curve
    * Functioning enthalpy models for each phase

    Parameters
    ----------
    constants : :obj:`ChemicalConstantsPackage <thermo.chemical_package.ChemicalConstantsPackage>` object
        Package of chemical constants; these are used as boundaries at times,
        initial guesses other times, and in all cases these properties are
        accessible as attributes of the resulting
        :obj:`EquilibriumState <thermo.equilibrium.EquilibriumState>` object, [-]
    correlations : :obj:`PropertyCorrelationsPackage <thermo.chemical_package.PropertyCorrelationsPackage>`
        Package of chemical T-dependent properties; these are used as boundaries at times,
        for initial guesses other times, and in all cases these properties are
        accessible as attributes of the resulting
        :obj:`EquilibriumState <thermo.equilibrium.EquilibriumState>` object, [-]
    gas : :obj:`Phase <thermo.phases.Phase>` object
        A single phase which can represent the gas phase, [-]
    liquids : list[:obj:`Phase <thermo.phases.Phase>`]
        A list of phase objects that can represent the liquid phases;
        if working with a VLL system with a consistent model, specify the same
        liquid phase twice; the length of this list is the maximum number of
        liquid phases that will be searched for, [-]
    solids : list[:obj:`Phase <thermo.phases.Phase>`]
        Not used, [-]
    settings : :obj:`BulkSettings <thermo.bulk.BulkSettings>` object
        Object containing settings for calculating bulk and transport
        properties, [-]

    Attributes
    ----------
    SS_NP_MAXITER : int
        Maximum number of sequential substitution iterations to try when
        converging a three or more phase solution, [-]
    SS_NP_TOL : float
        Convergence tolerance in sequential substitution for a three or more
        phase solution [-]
    SS_NP_TRIVIAL_TOL : float
        Tolerance at which to quick a three-phase flash because it is
        converging to the trivial solution, [-]
    SS_STAB_AQUEOUS_CHECK : bool
        If True, the first three-phase stability check will be on water (if
        it is present) as it forms a three-phase solution more than any
        other component, [-]
    DOUBLE_CHECK_2P : bool
        This parameter should be set to True if any issues in the solution are
        noticed. It can slow down two-phase solution. It ensures that all
        potential vapor-liquid and liquid-liquid phase pairs are searched for
        stability, instead of testing first for a vapor-liquid solution and
        then moving on to a three phase flash if an instability is detected,
        [-]

    Notes
    -----
    The algorithms in this object are mostly from [1]_, [2]_ and [3]_.
    Sequential substitution without acceleration is used by default to converge
    multiphase systems.

    Additional information that can be provided in the
    :obj:`ChemicalConstantsPackage <thermo.chemical_package.ChemicalConstantsPackage>`
    object and :obj:`PropertyCorrelationsPackage <thermo.chemical_package.PropertyCorrelationsPackage>`
    object that may help convergence is:

    * `Tc`, `Pc`, `omega`, `Tb`, and `atoms`
    * Gas heat capacity correlations
    * Liquid molar volume correlations
    * Heat of vaporization correlations

    Examples
    --------
    A three-phase flash of butanol, water, and ethanol with the SRK EOS without
    BIPs:

    >>> from thermo import ChemicalConstantsPackage, CEOSGas, CEOSLiquid, SRKMIX, FlashVLN, PropertyCorrelationsPackage, HeatCapacityGas
    >>> constants = ChemicalConstantsPackage(Tcs=[563.0, 647.14, 514.0], Pcs=[4414000.0, 22048320.0, 6137000.0], omegas=[0.59, 0.344, 0.635], MWs=[74.1216, 18.01528, 46.06844], CASs=['71-36-3', '7732-18-5', '64-17-5'])
    >>> properties = PropertyCorrelationsPackage(constants=constants,
    ...                                     HeatCapacityGases=[HeatCapacityGas(poly_fit=(50.0, 1000.0, [-3.787200194613107e-20, 1.7692887427654656e-16, -3.445247207129205e-13, 3.612771874320634e-10, -2.1953250181084466e-07, 7.707135849197655e-05, -0.014658388538054169, 1.5642629364740657, -7.614560475001724])),
    ...                                     HeatCapacityGas(poly_fit=(50.0, 1000.0, [5.543665000518528e-22, -2.403756749600872e-18, 4.2166477594350336e-15, -3.7965208514613565e-12, 1.823547122838406e-09, -4.3747690853614695e-07, 5.437938301211039e-05, -0.003220061088723078, 33.32731489750759])),
    ...                                     HeatCapacityGas(poly_fit=(50.0, 1000.0, [-1.162767978165682e-20, 5.4975285700787494e-17, -1.0861242757337942e-13, 1.1582703354362728e-10, -7.160627710867427e-08, 2.5392014654765875e-05, -0.004732593693568646, 0.5072291035198603, 20.037826650765965])),], )
    >>> eos_kwargs = dict(Tcs=constants.Tcs, Pcs=constants.Pcs, omegas=constants.omegas)
    >>> gas = CEOSGas(SRKMIX, eos_kwargs, HeatCapacityGases=properties.HeatCapacityGases)
    >>> liq = CEOSLiquid(SRKMIX, eos_kwargs, HeatCapacityGases=properties.HeatCapacityGases)
    >>> flashN = FlashVLN(constants, properties, liquids=[liq, liq], gas=gas)
    >>> res = flashN.flash(T=361, P=1e5, zs=[.25, 0.7, .05])
    >>> res.phase_count
    3


    References
    ----------
    .. [1] Michelsen, Michael L., and Jørgen M. Mollerup. Thermodynamic Models:
       Fundamentals & Computational Aspects. Tie-Line Publications, 2007.
    .. [2] Poling, Bruce E., John M. Prausnitz, and John P. O’Connell. The
       Properties of Gases and Liquids. 5th edition. New York: McGraw-Hill
       Professional, 2000.
    .. [3] Gmehling, Jürgen, Michael Kleiber, Bärbel Kolbe, and Jürgen Rarey.
       Chemical Thermodynamics for Process Simulation. John Wiley & Sons, 2019.
    '''
    SS_NP_MAXITER = FlashVL.PT_SS_MAXITER
    SS_NP_TRIVIAL_TOL = 5e-5
    SS_NP_TOL = 1e-15
    SS_STAB_AQUEOUS_CHECK = True

    DOUBLE_CHECK_2P = False

    SS_NP_STAB_HIGHEST_COMP_DIFF = False
    SS_NP_STAB_COMP_DIFF_MIN = None

    K_COMPOSITION_INDEPENDENT_HACK = True
    skip_solids = True
    
    supports_VF_flash = True
    supports_SF_flash = False

    def __init__(self, constants, correlations, liquids, gas, solids=None, settings=default_settings):
        self.constants = constants
        self.correlations = correlations
        self.liquids = liquids
        self.gas = gas
        self.settings = settings
        if solids:
            raise ValueError("Solids are not supported in this model")
        self._finish_initialization()

    def _finish_initialization(self):
        constants, correlations, settings = self.constants, self.correlations, self.settings
        liquids, gas = self.liquids, self.gas
        
        if gas is None:
            raise ValueError("Gas phase is required in this model")
        self.liquid0 = liquids[0] if liquids else None
        self.liquid_count = self.max_liquids = len(liquids)
        self.max_phases = 1 + self.max_liquids if gas is not None else self.max_liquids
        self.phases = [gas] + liquids if gas is not None else liquids


        liquids_to_unique_liquids = []
        unique_liquids, unique_liquid_hashes = [], []
        for i, l in enumerate(liquids):
            h = l.model_hash()
            if h not in unique_liquid_hashes:
                unique_liquid_hashes.append(h)
                unique_liquids.append(l)
                liquids_to_unique_liquids.append(i)
            else:
                liquids_to_unique_liquids.append(unique_liquid_hashes.index(h))
        if gas:
            gas_hash = gas.model_hash(True)

        gas_to_unique_liquid = None
        for i, l in enumerate(liquids):
            h = l.model_hash(True)
            if gas_hash == h:
                gas_to_unique_liquid = liquids_to_unique_liquids[i]
                break

        self.gas_to_unique_liquid = gas_to_unique_liquid
        self.liquids_to_unique_liquids = liquids_to_unique_liquids

        self.unique_liquids = unique_liquids
        self.unique_liquid_count = len(unique_liquids)
        self.unique_phases = [gas] + unique_liquids
        self.unique_phase_count = 1 + self.unique_liquid_count
        self.unique_liquid_hashes = unique_liquid_hashes

        self.N = constants.N

        self.K_composition_independent = all([i.composition_independent for i in self.phases])
        self.ideal_gas_basis = all([i.ideal_gas_basis for i in self.phases])


        self.aqueous_check = (self.SS_STAB_AQUEOUS_CHECK and '7732-18-5' in constants.CASs)
        self.stab = StabilityTester(Tcs=constants.Tcs, Pcs=constants.Pcs, omegas=constants.omegas,
                                    aqueous_check=self.aqueous_check, CASs=constants.CASs)


        self.T_MIN_FLASH = max(p.T_MIN_FLASH for p in self.phases)
        try:
            self.water_index = constants.CASs.index(CAS_H2O)
        except ValueError:
            self.water_index = None

#        self.flash_pure = FlashPureVLS(constants=constants, correlations=correlations,
#                                       gas=gas, liquids=unique_liquids, solids=[],
#                                       settings=settings)

    def flash_TVF(self, T, VF, zs, solution=None, hot_start=None, liquid_idx=None):
        if self.unique_liquid_count == 1:
            return self.flash_TVF_2P(T, VF, zs, self.liquids[0], self.gas, solution=solution, hot_start=hot_start)
        elif liquid_idx is not None:
            return self.flash_TVF_2P(T, VF, zs, self.liquids[liquid_idx], self.gas, solution=solution, hot_start=hot_start)
        else:
            sln_G_min, G_min = None, 1e100
            for l in self.unique_liquids:
                try:
                    sln = self.flash_TVF_2P(T, VF, zs, l, self.gas, solution=solution, hot_start=hot_start)
                    sln_G = (sln[1].G()*(1.0 - VF) + sln[2].G()*VF)
                    if sln_G < G_min:
                        sln_G_min, G_min = sln, sln_G
                except:
                    pass
            return sln_G_min


    def flash_PVF(self, P, VF, zs, solution=None, hot_start=None, liquid_idx=None):
        if self.unique_liquid_count == 1:
            sln_2P = self.flash_PVF_2P(P, VF, zs, self.liquids[0], self.gas, solution=solution, hot_start=hot_start)
        elif liquid_idx is not None:
            sln_2P = self.flash_PVF_2P(P, VF, zs, self.liquids[liquid_idx], self.gas, solution=solution, hot_start=hot_start)
        else:
            sln_G_min, G_min = None, 1e100
            for l in self.unique_liquids:
                try:
                    sln = self.flash_PVF_2P(P, VF, zs, l, self.gas, solution=solution, hot_start=hot_start)
                    sln_G = (sln[1].G()*(1.0 - VF) + sln[2].G()*VF)
                    if sln_G < G_min:
                        sln_G_min, G_min = sln, sln_G
                except:
                    pass
            sln_2P = sln_G_min
        return sln_2P


    def phases_at(self, T, P, zs, V=None):
        # Avoid doing excess work here
        # Goal: bring each phase to T, P, zs; using whatever duplicate information
        # possible
        # returns gas, [liquids], phases
        if V is None:
            gas = None
            gas_to_unique_liquid = self.gas_to_unique_liquid
            liquids = [None]*self.max_liquids
            for i, liq in enumerate(self.unique_liquids):
                l = liq.to(T=T, P=P, zs=zs)
                for j, idx in enumerate(self.liquids_to_unique_liquids):
                    if idx == i:
                        liquids[j] = l
                if i == gas_to_unique_liquid:
                    try:
                        gas = self.gas.to_TP_zs(T, P, zs, other_eos=l.eos_mix)
                    except:
                        gas = self.gas.to_TP_zs(T, P, zs)

            if gas is None:
                gas = self.gas.to(T=T, P=P, zs=zs)
            return gas, liquids, [gas] + liquids
        else:
            # TODO: handle unique liquids in this function
            if T is not None:
                gas = self.gas.to(T=T, V=V, zs=zs)
                liquids = [l.to(T=T, V=V, zs=zs) for l in self.liquids]
            elif P is not None:
                gas = self.gas.to(P=P, V=V, zs=zs)
                liquids = [l.to(P=P, V=V, zs=zs) for l in self.liquids]
            else:
                raise ValueError("Two of three specs are required")
            return gas, liquids, [gas] + liquids

    def flash_TPV_hot(self, T, P, V, zs, hot_start, solution=None):
        if hot_start.phase_count == 2:
            xs = hot_start.phases[0].zs
            ys = hot_start.phases[1].zs
            double_check_sln = self.flash_2P(T, P, zs, xs, ys, hot_start.phases[0],
                                                         hot_start.phases[1],
                                                         None, None, V_over_F_guess=hot_start.betas[1], LL=True)
            failed = not double_check_sln[0] and not double_check_sln[1]
            if not failed:
                return double_check_sln
        elif hot_start.phase_count > 2:
            phases = hot_start.phases
            comps = [i.zs for i in hot_start.phases]
            betas = hot_start.betas
            slnN = sequential_substitution_NP(
                T, P, zs, comps, betas, phases,
                maxiter=self.SS_NP_MAXITER, tol=self.SS_NP_TOL,
                trivial_solution_tol=self.SS_NP_TRIVIAL_TOL
            )
            return None, slnN[2], [], slnN[0], {'iterations': slnN[3], 'err': slnN[4],
                                                'stab_guess_name': None}
        if failed:
            return self.flash_TPV(T=T, P=P, V=V, zs=zs, solution=solution)


    def flash_TP_K_composition_idependent(self, T, P, zs):
        if self.max_phases == 1:
            phase = self.phases[0].to(T=T, P=P, zs=zs)
            return None, [phase], [], [1.0], {'iterations': 0, 'err': 0}


        Ks = liquid_phis = self.liquid0.phis_at(T, P, zs)
        try:
            VF, xs, ys = flash_inner_loop(zs, Ks, check=True)
        except PhaseCountReducedError:
            K_low, K_high = False, False
            for zi, Ki in zip(zs, Ks):
                if zi != 0.0:
                    if Ki > 1.0:
                        K_high = True
                    else:
                        K_low = True
            if K_low and not K_high:
                VF = -0.5
            elif K_high and not K_low:
                VF = 1.5
            else:
                raise ValueError("Error")

        if VF > 1.0:
            return None, [self.gas.to(T=T, P=P, zs=zs)], [], one_in_list, empty_flash_conv
        elif VF < 0.0:
            return None, [self.liquid0.to(T=T, P=P, zs=zs)], [], one_in_list, empty_flash_conv
        else:
            gas = self.gas.to(T=T, P=P, zs=ys)
            liquid = self.liquid0.to(T=T, P=P, zs=xs)
            return gas, [liquid], [], [VF, 1.0 - VF], empty_flash_conv

    def flash_TPV(self, T, P, V, zs=None, solution=None, hot_start=None):
        if hot_start is not None and hot_start.phase_count > 1 and solution is None:
            # Only allow hot start when there are multiple phases
            try:
                return self.flash_TPV_hot(T, P, V, zs, hot_start, solution=solution)
            except Exception as e:
                # Let anything fail
                pass
        if self.K_composition_independent and self.K_COMPOSITION_INDEPENDENT_HACK and solution is None:
            return self.flash_TP_K_composition_idependent(T, P, zs)

        gas, liquids, phases = self.phases_at(T, P, zs, V=V)
#        if self.K_composition_independent and self.K_COMPOSITION_INDEPENDENT_HACK:
#            # TODO move into new function?
#            if self.max_phases == 2:
#                gas_phis = gas.phis()
#                liquid_phis = liquids[0].phis()
#                Ks = [liquid_phis[i]/gas_phis[i] for i in range(self.N)]
#                VF, xs, ys = flash_inner_loop(zs, Ks)
#                if VF > 1.0:
#                    return None, [gas], [], one_in_list, empty_flash_conv
#                elif VF < 0.0:
#                    return None, [liquids[0]], [], one_in_list, empty_flash_conv
#                else:
#                    gas = gas.to(T=T, P=P, zs=ys)
#                    liquid = liquids[0].to(T=T, P=P, zs=xs)
#                    return gas, [liquid], [], [VF, 1.0 - VF], empty_flash_conv

        min_phase_1P, G_min_1P = None, 1e100
        ideal_gas_basis = self.ideal_gas_basis
        if ideal_gas_basis:
            for p in phases:
                G = p.G_min_criteria()
                if G < G_min_1P:
                    min_phase_1P, G_min_1P = p, G
        else:
            for p in phases:
                G = p.G()
                if G < G_min_1P:
                    min_phase_1P, G_min_1P = p, G
        one_phase_sln = None, [min_phase_1P], [], one_in_list, empty_flash_conv

        sln_2P, one_phase_min = None, None
        VL_solved, LL_solved = False, False
        phase_evolved = [False]*self.max_phases

        if T is None or P is None:
            raise NotImplementedError("Multiphase isochoric flashes are not yet implemented")

        try:
            sln_2P = self.flash_TP_stability_test(T, P, zs, liquids[0], gas, solution=solution, phases_ready=True)
            if len(sln_2P[3]) == 2: # One phase only
                VL_solved = True
                g, l0 = sln_2P[0], sln_2P[1][0]
                found_phases = [g, l0]
                phase_evolved[0] = phase_evolved[1] = True
                found_betas = sln_2P[3]
        except:
            VL_solved = False

        if not VL_solved and self.max_liquids > 1:
            for n_liq, a_liq in enumerate(liquids[1:]):
                # Come up with algorithm to skip
                try:
                    sln_2P = self.flash_TP_stability_test(T, P, zs, liquids[0], a_liq, solution=solution, LL=True)
                    if len(sln_2P[3]) == 2:
                        LL_solved = True
                        g = None
                        l0, l1 = sln_2P[1]
                        found_phases = [l0, l1]
                        found_betas = sln_2P[3]
                        break
                except:
                    pass
        if not LL_solved and not VL_solved:
            found_phases = [min_phase_1P]
            found_betas = [1]

        existing_comps = [i.zs for i in found_phases]
        if ideal_gas_basis:
            G_2P = sum([found_betas[i]*found_phases[i].G_min_criteria() for i in range(len(found_phases))])
        else:
            G_2P = sum([found_betas[i]*found_phases[i].G() for i in range(len(found_phases))])

        if sln_2P is not None and self.DOUBLE_CHECK_2P:
            g_id, ls_id, _, _ = identify_sort_phases(found_phases, found_betas, self.constants,
                                                    self.correlations, settings=self.settings,
                                                    skip_solids=self.skip_solids)
            if g_id is None:
                another_phase, base_phase = gas, liquids[0]
            else:
                another_phase, base_phase = liquids[0], gas

            all_solutions = self.stability_test_Michelsen(T, P, zs, another_phase, base_phase, all_solutions=True) + self.stability_test_Michelsen(T, P, zs, base_phase, another_phase, all_solutions=True)
            all_solutions = deduplicate_stab_results(all_solutions)
            for stab_sln in all_solutions:
                trial_zs, appearing_zs, V_over_F, stab_guess_name, _, _, _ = stab_sln
                if V_over_F < 1.000001 and V_over_F > -.000001:
                    try:
                        double_check_sln = self.flash_2P(T, P, zs, trial_zs, appearing_zs, another_phase,
                                                         base_phase, gas, liquids[0], V_over_F_guess=V_over_F, LL=True)
                    except (UnconvergedError, OscillationError, PhaseCountReducedError):
                        continue
                    double_check_betas = double_check_sln[3]
                    if len(double_check_betas) == 2:
                        double_check_phases = double_check_sln[1]
                        if ideal_gas_basis:
                            G_2P_new = sum([double_check_betas[i]*double_check_phases[i].G_min_criteria() for i in range(2)])
                        else:
                            G_2P_new = sum([double_check_betas[i]*double_check_phases[i].G() for i in range(2)])
                        if G_2P_new < G_2P:
                            sln_2P = double_check_sln
                            G_2P = G_2P_new
                            found_phases = double_check_phases
                            existing_comps = [i.zs for i in found_phases]
                            found_betas = double_check_betas


        # Can still be a VLL solution now that a new phase has been added
        if (LL_solved and (self.max_liquids == 2) or (VL_solved and self.max_liquids == 1) or (self.N < 3 and (VL_solved or LL_solved))):
            # Check the Gibbs
            if G_2P < G_min_1P:
                return sln_2P
            else:
                # May be missing possible 3 phase solutions which have lower G
                return one_phase_sln
        if not LL_solved and not VL_solved:
            return one_phase_sln
        if self.N < 3:
            # Gibbs phase rule 5.9: Multiphase Split and Stability Analysis
            # in Thermodynamics and Applications in Hydrocarbon Energy Production by Firoozabadi (2016)
            # Can only have three phases when either T or P are not specified
            return sln_2P

        # Always want the other phase to be type of one not present.
        min_phase = sln_2P[0] if sln_2P[0] is not None else sln_2P[1][0]
        other_phase_flashed = found_phases[0] if found_phases[0] is not min_phase else found_phases[1]
        other_phase = self.gas if LL_solved else liquids[1]

        SWITCH_EXPECT_LIQ_Z = 0.25
        expect_liquid = (True if (other_phase_flashed.Z() > SWITCH_EXPECT_LIQ_Z
                                 or min_phase.Z() > SWITCH_EXPECT_LIQ_Z) else False)
        expect_aqueous = False
        if self.aqueous_check and self.water_index is not None and zs[self.water_index] > 1e-3:
            # Probably a water phase exists
            expect_aqueous = True

        stable, (trial_zs, appearing_zs, V_over_F, stab_guess_name, stab_guess_number, stab_sum_zs_test, stab_lnK_2_tot) = self.stability_test_Michelsen(
                T, P, zs, min_phase, other_phase, existing_comps=existing_comps, expect_liquid=expect_liquid,
                expect_aqueous=expect_aqueous, handle_iffy=False, highest_comp_diff=self.SS_NP_STAB_HIGHEST_COMP_DIFF, min_comp_diff=self.SS_NP_STAB_COMP_DIFF_MIN)
        if stable and self.unique_liquid_count > 2:
            for other_phase in liquids[2:]:
                stable, (trial_zs, appearing_zs, V_over_F, stab_guess_name, stab_guess_number, stab_sum_zs_test, stab_lnK_2_tot) = self.stability_test_Michelsen(T, P, zs,
                                                                                                            min_phase,
                                                                                                            other_phase, existing_comps=existing_comps)
                if not stable:
                    break
        if stable:
            # Return the two phase solution
            return sln_2P
        else:
            flash_phases = found_phases + [other_phase]
            flash_comps = [i.zs for i in found_phases]
            flash_comps.append(appearing_zs)
            flash_betas = list(found_betas)
            flash_betas.append(0.0)
            try_LL_3P_failed = False
            try:
                failed_3P = False

                sln3 = sequential_substitution_NP(
                    T, P, zs, flash_comps, flash_betas, flash_phases, 
                    maxiter=self.SS_NP_MAXITER, tol=self.SS_NP_TOL,
                    trivial_solution_tol=self.SS_NP_TRIVIAL_TOL
                )
                if ideal_gas_basis:
                    G_3P = sum([sln3[0][i]*sln3[2][i].G_min_criteria() for i in range(3)])
                else:
                    G_3P = sum([sln3[0][i]*sln3[2][i].G() for i in range(3)])
                new_betas = sln3[0]
                good_betas = True
                for b in new_betas:
                    if b < 0.0 or b > 1.0:
                        good_betas = False
                if self.max_phases == 3 and good_betas:
                    if G_2P < G_3P:
                        raise ValueError("Should never happen")
                    return None, sln3[2], [], sln3[0], {'iterations': sln3[3], 'err': sln3[4],
                                                                   'stab_guess_name': stab_guess_name, 'G_2P': G_2P}
                if not good_betas or G_3P > G_2P:
                    # Might need to make this true
                    try_LL_3P_failed = False
                    failed_3P = True
            except:
                try_LL_3P_failed = True
                failed_3P = True
            if VL_solved and failed_3P:
                if try_LL_3P_failed:
                    try:
                        V_over_F, xs, ys, l, g, iteration, err = sequential_substitution_2P(
                            T=T, P=P, V=None,
                            zs=zs, xs_guess=trial_zs,
                            ys_guess=appearing_zs,
                            liquid_phase=liquids[0],
                            gas_phase=liquids[1],
                            maxiter=self.PT_SS_POLISH_MAXITER,
                            tol=self.PT_SS_POLISH_TOL,
                            V_over_F_guess=V_over_F
                        )
                        if ideal_gas_basis:
                            new_G_2P = V_over_F*g.G_min_criteria() + (1.0 - V_over_F)*l.G_min_criteria()
                        else:
                            new_G_2P = V_over_F*g.G() + (1.0 - V_over_F)*l.G()
                        if new_G_2P < G_2P:
                            return None, [l, g], [], [1.0 - V_over_F, V_over_F], {'iterations': iteration, 'err': err,
                                         'stab_guess_name': stab_guess_name, 'G_2P': G_2P}
                            a = 1
                        else:
                            return sln_2P
                    except TrivialSolutionError:
                        return sln_2P
                else:
                    return sln_2P

        slnN = sln3

        if self.N == 3:
            # Cannot have a four phase system with three components (and so on)
            return slnN

        # We are here after solving three phases
        liquid_idx = 2
        while len(slnN[0]) < self.max_phases and liquid_idx < self.max_liquids:
            min_phase = slnN[2][0]
            existing_comps = slnN[1]
            # hardcoded for now - need to track
            other_phase = liquids[liquid_idx]
            stable, (trial_zs, appearing_zs, V_over_F, stab_guess_name, stab_guess_number, stab_sum_zs_test, stab_lnK_2_tot) = self.stability_test_Michelsen(T, P, zs, min_phase, other_phase, existing_comps=existing_comps)
        # if stable and self.unique_liquid_count > 3:
        #     for other_phase in liquids[3:]:
        #         stable, (trial_zs, appearing_zs, V_over_F, stab_guess_name, stab_guess_number, stab_sum_zs_test, stab_lnK_2_tot) = self.stability_test_Michelsen(T, P, zs,
        #                                                                                                     min_phase,
        #                                                                                                     other_phase, existing_comps=existing_comps)
        #         if not stable:
        #             break

            if not stable:

                flash_phases = slnN[2] + [other_phase]
                flash_comps = list(slnN[1])
                flash_comps.append(appearing_zs)
                flash_betas = list(slnN[0])
                flash_betas.append(0.0)
                try:
                    slnN = sequential_substitution_NP(
                        T, P, zs, flash_comps, flash_betas, flash_phases
                    )
                    if self.max_phases == len(slnN[0]):
                        return None, slnN[2], [], slnN[0], {'iterations': slnN[3], 'err': slnN[4],
                                                                       'stab_guess_name': stab_guess_name, 'G_2P': G_2P}
                except:
                    pass

            liquid_idx += 1

        return None, slnN[2], [], slnN[0], {'iterations': slnN[3], 'err': slnN[4],
                                            'stab_guess_name': stab_guess_name, 'G_2P': G_2P}

    # Should be straightforward for stability test
    # How handle which phase to stability test? May need both
    # After 2 phase flash, drop into 3 phase flash
    # Start with water-methane-octanol example?

    # Vapor fraction flashes - if anything other than VF=1, need a 3 phase stability test
