##############################################################################
# Institute for the Design of Advanced Energy Systems Process Systems
# Engineering Framework (IDAES PSE Framework) Copyright (c) 2018-2020, by the
# software owners: The Regents of the University of California, through
# Lawrence Berkeley National Laboratory,  National Technology & Engineering
# Solutions of Sandia, LLC, Carnegie Mellon University, West Virginia
# University Research Corporation, et al. All rights reserved.
#
# Please see the files COPYRIGHT.txt and LICENSE.txt for full copyright and
# license information, respectively. Both files are also available online
# at the URL "https://github.com/IDAES/idaes-pse".
##############################################################################
"""
Tests for ControlVolumeBlockData.

Author: Andrew Lee
"""
import pytest

from pyomo.environ import (ConcreteModel,
                           Constraint,
                           Param,
                           RangeSet,
                           Set,
                           Var,
                           TerminationCondition,
                           SolverStatus,
                           value,
                           units)
from pyomo.util.check_units import (assert_units_consistent,
                                    assert_units_equivalent)
from pyomo.network import Port
from pyomo.common.config import ConfigBlock

from idaes.core import (FlowsheetBlock,
                        StateBlock,
                        declare_process_block_class,
                        StateBlockData,
                        StateBlock,
                        PhysicalParameterBlock,
                        MaterialBalanceType,
                        EnergyBalanceType)
from idaes.generic_models.properties.activity_coeff_models.BTX_activity_coeff_VLE \
    import BTXParameterBlock
from idaes.generic_models.properties import iapws95
from idaes.generic_models.properties.examples.saponification_thermo import \
    SaponificationParameterBlock

from idaes.generic_models.unit_models.mixer import (Mixer,
                                                    MixerData,
                                                    MixingType,
                                                    MomentumMixingType)
from idaes.core.util.exceptions import (BurntToast,
                                        ConfigurationError,
                                        PropertyNotSupportedError)
from idaes.core.util.model_statistics import (degrees_of_freedom,
                                              number_variables,
                                              number_total_constraints,
                                              number_unused_variables)
from idaes.core.util.testing import (PhysicalParameterTestBlock,
                                     TestStateBlock,
                                     initialization_tester)
from idaes.core.util import get_solver


# -----------------------------------------------------------------------------
# Get default solver for testing
solver = get_solver()


# -----------------------------------------------------------------------------
# Unit Tests for Mixer
class TestMixer(object):
    @declare_process_block_class("MixerFrame")
    class MixerFrameData(MixerData):
        def build(self):
            super(MixerData, self).build()

    @pytest.fixture(scope="function")
    def mixer_frame(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = PhysicalParameterTestBlock()

        m.fs.mix = MixerFrame(default={"property_package": m.fs.pp})

        return m

    @pytest.mark.unit
    def test_mixer_config(self, mixer_frame):
        assert len(mixer_frame.fs.mix.config) == 12
        assert mixer_frame.fs.mix.config.dynamic is False
        assert mixer_frame.fs.mix.config.has_holdup is False
        assert mixer_frame.fs.mix.config.property_package == mixer_frame.fs.pp
        assert isinstance(mixer_frame.fs.mix.config.property_package_args,
                          ConfigBlock)
        assert len(mixer_frame.fs.mix.config.property_package_args) == 0
        assert mixer_frame.fs.mix.config.inlet_list is None
        assert mixer_frame.fs.mix.config.num_inlets is None
        assert mixer_frame.fs.mix.config.has_phase_equilibrium is False
        assert mixer_frame.fs.mix.config.energy_mixing_type == \
            MixingType.extensive
        assert mixer_frame.fs.mix.config.momentum_mixing_type == \
            MomentumMixingType.minimize
        assert mixer_frame.fs.mix.config.mixed_state_block is None
        assert mixer_frame.fs.mix.config.construct_ports is True
        assert mixer_frame.fs.mix.config.material_balance_type == \
            MaterialBalanceType.useDefault

    @pytest.mark.unit
    def test_inherited_methods(self, mixer_frame):
        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        assert hasattr(mixer_frame.fs.mix.config.property_package,
                       "phase_list")

    @pytest.mark.unit
    def test_create_inlet_list_default(self, mixer_frame):
        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()

        for i in inlet_list:
            assert i in ["inlet_1", "inlet_2"]

    @pytest.mark.unit
    def test_create_inlet_list_inlet_list(self, mixer_frame):
        mixer_frame.fs.mix.config.inlet_list = ["foo", "bar"]

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()

        for i in inlet_list:
            assert i in ["foo", "bar"]

    @pytest.mark.unit
    def test_create_inlet_list_num_inlets(self, mixer_frame):
        mixer_frame.fs.mix.config.num_inlets = 3

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()

        for i in inlet_list:
            assert i in ["inlet_1", "inlet_2", "inlet_3"]

    @pytest.mark.unit
    def test_create_inlet_list_both_args_consistent(self, mixer_frame):
        mixer_frame.fs.mix.config.inlet_list = ["foo", "bar"]
        mixer_frame.fs.mix.config.num_inlets = 2

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()

        for i in inlet_list:
            assert i in ["foo", "bar"]

    @pytest.mark.unit
    def test_create_inlet_list_both_args_inconsistent(self, mixer_frame):
        mixer_frame.fs.mix.config.inlet_list = ["foo", "bar"]
        mixer_frame.fs.mix.config.num_inlets = 3

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        with pytest.raises(ConfigurationError):
            mixer_frame.fs.mix.create_inlet_list()

    @pytest.mark.unit
    def test_add_inlet_state_blocks(self, mixer_frame):
        mixer_frame.fs.mix.config.inlet_list = ["foo", "bar"]

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)

        assert isinstance(mixer_frame.fs.mix.foo_state, StateBlock)
        assert isinstance(mixer_frame.fs.mix.bar_state, StateBlock)

        assert len(inlet_blocks) == 2
        for i in inlet_blocks:
            assert isinstance(i, StateBlock)
            assert i.local_name in ["foo_state", "bar_state"]
            assert i[0].config.has_phase_equilibrium is False
            assert i[0].config.defined_state is True
            assert len(i[0].config) == 3

    @pytest.mark.unit
    def test_add_inlet_state_blocks_prop_pack_args(self, mixer_frame):
        mixer_frame.fs.mix.config.property_package_args = {"test": 1}
        mixer_frame.fs.mix.config.inlet_list = ["foo", "bar"]

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)

        assert isinstance(mixer_frame.fs.mix.foo_state, StateBlock)
        assert isinstance(mixer_frame.fs.mix.bar_state, StateBlock)

        assert len(inlet_blocks) == 2
        for i in inlet_blocks:
            assert isinstance(i, StateBlock)
            assert i.local_name in ["foo_state", "bar_state"]
            assert i[0].config.has_phase_equilibrium is False
            assert i[0].config.defined_state is True
            assert len(i[0].config) == 4
            assert i[0].config.test == 1

    @pytest.mark.unit
    def test_add_mixed_state_block(self, mixer_frame):
        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        assert isinstance(mixed_block, StateBlock)
        assert hasattr(mixer_frame.fs.mix, "mixed_state")
        assert not mixer_frame.fs.mix.mixed_state[0].config.has_phase_equilibrium
        assert mixer_frame.fs.mix.mixed_state[0].config.defined_state is False
        assert len(mixer_frame.fs.mix.mixed_state[0].config) == 3

    @pytest.mark.unit
    def test_add_mixed_state_block_prop_pack_args(self, mixer_frame):
        mixer_frame.fs.mix.config.property_package_args = {"test": 1}

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        assert isinstance(mixed_block, StateBlock)
        assert hasattr(mixer_frame.fs.mix, "mixed_state")
        assert not mixer_frame.fs.mix.mixed_state[0].config.has_phase_equilibrium
        assert mixer_frame.fs.mix.mixed_state[0].config.defined_state is False
        assert len(mixer_frame.fs.mix.mixed_state[0].config) == 4
        assert mixer_frame.fs.mix.mixed_state[0].config.test == 1

    @pytest.mark.unit
    def test_get_mixed_state_block(self, mixer_frame):
        mixer_frame.fs.sb = TestStateBlock(
                mixer_frame.fs.time,
                default={"parameters": mixer_frame.fs.pp})

        mixer_frame.fs.mix.config.mixed_state_block = mixer_frame.fs.sb

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        mixed_block = mixer_frame.fs.mix.get_mixed_state_block()

        assert mixed_block == mixer_frame.fs.sb

    @pytest.mark.unit
    def test_get_mixed_state_block_none(self, mixer_frame):
        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        with pytest.raises(BurntToast):
            mixer_frame.fs.mix.get_mixed_state_block()

    @pytest.mark.unit
    def test_get_mixed_state_block_mismatch(self, mixer_frame):
        mixer_frame.fs.sb = TestStateBlock(
                mixer_frame.fs.time,
                default={"parameters": mixer_frame.fs.pp})

        # Change parameters arg to create mismatch
        mixer_frame.fs.sb[0].config.parameters = None

        mixer_frame.fs.mix.config.mixed_state_block = mixer_frame.fs.sb

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        with pytest.raises(ConfigurationError):
            mixer_frame.fs.mix.get_mixed_state_block()

    # Test mixing equation methods
    @pytest.mark.unit
    def test_add_material_mixing_equations_pc(self, mixer_frame):
        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_material_mixing_equations(
                inlet_blocks, mixed_block, MaterialBalanceType.componentPhase)

        assert isinstance(mixer_frame.fs.mix.material_mixing_equations,
                          Constraint)
        assert len(mixer_frame.fs.mix.material_mixing_equations) == 4

    @pytest.mark.unit
    def test_add_material_mixing_equations_pc_equilibrium(self, mixer_frame):
        mixer_frame.fs.mix.config.has_phase_equilibrium = True

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_material_mixing_equations(
                inlet_blocks, mixed_block, MaterialBalanceType.componentPhase)

        assert isinstance(mixer_frame.fs.mix.phase_equilibrium_generation,
                          Var)
        assert isinstance(mixer_frame.fs.mix.material_mixing_equations,
                          Constraint)
        assert len(mixer_frame.fs.mix.material_mixing_equations) == 4

    @pytest.mark.unit
    def test_add_material_mixing_equations_pc_equilibrium_not_supported(
            self, mixer_frame):
        mixer_frame.fs.mix.config.has_phase_equilibrium = True

        # Remove phase equilibrium list to trigger error
        mixer_frame.fs.pp.del_component(
                mixer_frame.fs.pp.phase_equilibrium_idx)

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        with pytest.raises(PropertyNotSupportedError):
            mixer_frame.fs.mix.add_material_mixing_equations(
                inlet_blocks, mixed_block, MaterialBalanceType.componentPhase)

    @pytest.mark.unit
    def test_add_material_mixing_equations_tc(self, mixer_frame):
        mixer_frame.fs.mix.config.material_balance_type = \
            MaterialBalanceType.componentTotal

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_material_mixing_equations(
                inlet_blocks, mixed_block, MaterialBalanceType.componentTotal)

        assert isinstance(mixer_frame.fs.mix.material_mixing_equations,
                          Constraint)
        assert len(mixer_frame.fs.mix.material_mixing_equations) == 2

    @pytest.mark.unit
    def test_add_material_mixing_equations_tc_equilibrium(self, mixer_frame):
        mixer_frame.fs.mix.config.material_balance_type = \
            MaterialBalanceType.componentTotal
        mixer_frame.fs.mix.config.has_phase_equilibrium = True

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_material_mixing_equations(
                inlet_blocks, mixed_block, MaterialBalanceType.componentTotal)

        assert isinstance(mixer_frame.fs.mix.material_mixing_equations,
                          Constraint)
        assert len(mixer_frame.fs.mix.material_mixing_equations) == 2

    @pytest.mark.unit
    def test_add_material_mixing_equations_t(self, mixer_frame):
        mixer_frame.fs.mix.config.material_balance_type = \
            MaterialBalanceType.total

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_material_mixing_equations(
                inlet_blocks, mixed_block, MaterialBalanceType.total)

        assert isinstance(mixer_frame.fs.mix.material_mixing_equations,
                          Constraint)
        assert len(mixer_frame.fs.mix.material_mixing_equations) == 1

    @pytest.mark.unit
    def test_add_material_mixing_equations_t_equilibrium(self, mixer_frame):
        mixer_frame.fs.mix.config.material_balance_type = \
            MaterialBalanceType.total
        mixer_frame.fs.mix.config.has_phase_equilibrium = True

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_material_mixing_equations(
                inlet_blocks, mixed_block, MaterialBalanceType.total)

        assert isinstance(mixer_frame.fs.mix.material_mixing_equations,
                          Constraint)
        assert len(mixer_frame.fs.mix.material_mixing_equations) == 1

    @pytest.mark.unit
    def test_add_material_mixing_equations_e(self, mixer_frame):
        mixer_frame.fs.mix.config.material_balance_type = \
            MaterialBalanceType.elementTotal
        mixer_frame.fs.mix.config.has_phase_equilibrium = True

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        with pytest.raises(ConfigurationError):
            mixer_frame.fs.mix.add_material_mixing_equations(
                inlet_blocks, mixed_block, MaterialBalanceType.elementTotal)

    @pytest.mark.unit
    def test_add_material_mixing_equations_none(self, mixer_frame):
        mixer_frame.fs.mix.config.material_balance_type = \
            MaterialBalanceType.none
        mixer_frame.fs.mix.config.has_phase_equilibrium = True

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_material_mixing_equations(
                inlet_blocks, mixed_block, MaterialBalanceType.none)

        assert not hasattr(mixer_frame.fs.mix, "material_mixing_equations")

    @pytest.mark.unit
    def test_add_energy_mixing_equations(self, mixer_frame):
        mixer_frame.fs.mix.config.has_phase_equilibrium = True

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_energy_mixing_equations(inlet_blocks,
                                                       mixed_block)

        assert isinstance(mixer_frame.fs.mix.enthalpy_mixing_equations,
                          Constraint)
        assert len(mixer_frame.fs.mix.enthalpy_mixing_equations) == 1

    @pytest.mark.unit
    def test_add_pressure_minimization_equations(self, mixer_frame):
        mixer_frame.fs.mix.config.has_phase_equilibrium = True

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_pressure_minimization_equations(inlet_blocks,
                                                               mixed_block)

        assert mixer_frame.fs.mix.inlet_idx.type() is RangeSet
        assert isinstance(mixer_frame.fs.mix.minimum_pressure, Var)
        assert len(mixer_frame.fs.mix.minimum_pressure) == 2
        assert isinstance(mixer_frame.fs.mix.eps_pressure, Param)
        assert isinstance(mixer_frame.fs.mix.minimum_pressure_constraint,
                          Constraint)
        assert len(mixer_frame.fs.mix.minimum_pressure) == 2
        assert isinstance(mixer_frame.fs.mix.mixture_pressure, Constraint)

    @pytest.mark.unit
    def test_add_pressure_equality_equations(self, mixer_frame):
        mixer_frame.fs.mix.config.has_phase_equilibrium = True

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_pressure_equality_equations(inlet_blocks,
                                                           mixed_block)

        assert isinstance(mixer_frame.fs.mix.pressure_equality_constraints,
                          Constraint)
        assert len(mixer_frame.fs.mix.pressure_equality_constraints) == 2

    @pytest.mark.unit
    def test_add_port_objects(self, mixer_frame):
        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_port_objects(inlet_list,
                                            inlet_blocks,
                                            mixed_block)

        assert isinstance(mixer_frame.fs.mix.inlet_1, Port)
        assert isinstance(mixer_frame.fs.mix.inlet_2, Port)
        assert isinstance(mixer_frame.fs.mix.outlet, Port)

    @pytest.mark.unit
    def test_add_port_objects_construct_ports_False(self, mixer_frame):
        mixer_frame.fs.mix.config.construct_ports = False

        mixer_frame.fs.mix._get_property_package()
        mixer_frame.fs.mix._get_indexing_sets()

        inlet_list = mixer_frame.fs.mix.create_inlet_list()
        inlet_blocks = mixer_frame.fs.mix.add_inlet_state_blocks(inlet_list)
        mixed_block = mixer_frame.fs.mix.add_mixed_state_block()

        mixer_frame.fs.mix.add_port_objects(inlet_list,
                                            inlet_blocks,
                                            mixed_block)

        assert hasattr(mixer_frame.fs.mix, "inlet_1") is False
        assert hasattr(mixer_frame.fs.mix, "inlet_2") is False
        assert hasattr(mixer_frame.fs.mix, "outlet") is False

    # -------------------------------------------------------------------------
    # Test build method
    @pytest.mark.build
    @pytest.mark.unit
    def test_build_default(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = PhysicalParameterTestBlock()

        m.fs.mix = Mixer(default={"property_package": m.fs.pp})

        assert isinstance(m.fs.mix.material_mixing_equations, Constraint)
        assert len(m.fs.mix.material_mixing_equations) == 4
        assert hasattr(m.fs.mix, "phase_equilibrium_idx_ref") is False

        assert isinstance(m.fs.mix.enthalpy_mixing_equations, Constraint)
        assert len(m.fs.mix.enthalpy_mixing_equations) == 1

        assert m.fs.mix.inlet_idx.type() is RangeSet
        assert isinstance(m.fs.mix.minimum_pressure, Var)
        assert len(m.fs.mix.minimum_pressure) == 2
        assert isinstance(m.fs.mix.eps_pressure, Param)
        assert isinstance(m.fs.mix.minimum_pressure_constraint, Constraint)
        assert len(m.fs.mix.minimum_pressure) == 2
        assert isinstance(m.fs.mix.mixture_pressure, Constraint)

        assert isinstance(m.fs.mix.inlet_1, Port)
        assert isinstance(m.fs.mix.inlet_2, Port)
        assert isinstance(m.fs.mix.outlet, Port)

    @pytest.mark.build
    @pytest.mark.unit
    def test_build_phase_equilibrium(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = PhysicalParameterTestBlock()

        m.fs.mix = Mixer(default={"property_package": m.fs.pp,
                                  "has_phase_equilibrium": True})

        assert isinstance(m.fs.mix.material_mixing_equations, Constraint)
        assert len(m.fs.mix.material_mixing_equations) == 4
        assert isinstance(m.fs.mix.phase_equilibrium_generation, Var)

        assert isinstance(m.fs.mix.enthalpy_mixing_equations, Constraint)
        assert len(m.fs.mix.enthalpy_mixing_equations) == 1

        assert m.fs.mix.inlet_idx.type() is RangeSet
        assert isinstance(m.fs.mix.minimum_pressure, Var)
        assert len(m.fs.mix.minimum_pressure) == 2
        assert isinstance(m.fs.mix.eps_pressure, Param)
        assert isinstance(m.fs.mix.minimum_pressure_constraint, Constraint)
        assert len(m.fs.mix.minimum_pressure) == 2
        assert isinstance(m.fs.mix.mixture_pressure, Constraint)

        assert isinstance(m.fs.mix.inlet_1, Port)
        assert isinstance(m.fs.mix.inlet_2, Port)
        assert isinstance(m.fs.mix.outlet, Port)

    @pytest.mark.build
    @pytest.mark.unit
    def test_build_phase_pressure_equality(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = PhysicalParameterTestBlock()

        m.fs.mix = Mixer(default={
                "property_package": m.fs.pp,
                "momentum_mixing_type": MomentumMixingType.equality})

        assert isinstance(m.fs.mix.material_mixing_equations, Constraint)
        assert len(m.fs.mix.material_mixing_equations) == 4

        assert isinstance(m.fs.mix.enthalpy_mixing_equations, Constraint)
        assert len(m.fs.mix.enthalpy_mixing_equations) == 1

        assert isinstance(m.fs.mix.pressure_equality_constraints, Constraint)
        assert len(m.fs.mix.pressure_equality_constraints) == 2

        assert isinstance(m.fs.mix.inlet_1, Port)
        assert isinstance(m.fs.mix.inlet_2, Port)
        assert isinstance(m.fs.mix.outlet, Port)

    # -------------------------------------------------------------------------
    # Test models checks, initialize and release state methods
    @pytest.mark.unit
    def test_model_checks(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = PhysicalParameterTestBlock()

        m.fs.mix = Mixer(default={
                "property_package": m.fs.pp,
                "momentum_mixing_type": MomentumMixingType.equality})

        m.fs.mix.model_check()

        assert m.fs.mix.inlet_1_state[0].check is True
        assert m.fs.mix.inlet_2_state[0].check is True
        assert m.fs.mix.mixed_state[0].check is True

    @pytest.mark.initialization
    @pytest.mark.component
    def test_initialize(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = PhysicalParameterTestBlock()
        m.fs.sb = TestStateBlock(m.fs.time, default={"parameters": m.fs.pp})

        m.fs.mix = Mixer(default={
                "property_package": m.fs.pp,
                "mixed_state_block": m.fs.sb})

        # Change one inlet pressure to check initialization calculations
        m.fs.mix.inlet_1_state[0].pressure = 8e4

        f = m.fs.mix.initialize(hold_state=True)

        assert m.fs.mix.inlet_1_state[0].init_test is True
        assert m.fs.mix.inlet_2_state[0].init_test is True
        assert m.fs.sb[0].init_test is True
        assert m.fs.mix.inlet_1_state[0].hold_state is True
        assert m.fs.mix.inlet_2_state[0].hold_state is True
        assert m.fs.sb[0].hold_state is False

        assert m.fs.sb[0].flow_mol_phase_comp["p1", "c1"].value == 4
        assert m.fs.sb[0].flow_mol_phase_comp["p1", "c2"].value == 4
        assert m.fs.sb[0].flow_mol_phase_comp["p2", "c1"].value == 4
        assert m.fs.sb[0].flow_mol_phase_comp["p2", "c2"].value == 4

        assert m.fs.sb[0].temperature.value == 300

        assert m.fs.sb[0].pressure.value == 8e4

        m.fs.mix.release_state(flags=f)

        assert m.fs.mix.inlet_1_state[0].hold_state is False
        assert m.fs.mix.inlet_2_state[0].hold_state is False
        assert m.fs.sb[0].hold_state is False

    @pytest.mark.ui
    @pytest.mark.component
    def test_report(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = PhysicalParameterTestBlock()
        m.fs.sb = TestStateBlock(m.fs.time, default={"parameters": m.fs.pp})

        m.fs.mix = Mixer(default={
                "property_package": m.fs.pp})

        m.fs.mix.report()


# -----------------------------------------------------------------------------
class TestBTX(object):
    @pytest.fixture(scope="class")
    def btx(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})

        m.fs.properties = BTXParameterBlock(default={"valid_phase": 'Liq'})

        m.fs.unit = Mixer(default={"property_package": m.fs.properties})

        m.fs.unit.inlet_1.flow_mol[0].fix(5)  # mol/s
        m.fs.unit.inlet_1.temperature[0].fix(365)  # K
        m.fs.unit.inlet_1.pressure[0].fix(2e5)  # Pa
        m.fs.unit.inlet_1.mole_frac_comp[0, "benzene"].fix(0.5)
        m.fs.unit.inlet_1.mole_frac_comp[0, "toluene"].fix(0.5)

        m.fs.unit.inlet_2.flow_mol[0].fix(1)  # mol/s
        m.fs.unit.inlet_2.temperature[0].fix(300)  # K
        m.fs.unit.inlet_2.pressure[0].fix(101325)  # Pa
        m.fs.unit.inlet_2.mole_frac_comp[0, "benzene"].fix(0.5)
        m.fs.unit.inlet_2.mole_frac_comp[0, "toluene"].fix(0.5)

        return m

    @pytest.mark.build
    @pytest.mark.unit
    def test_build(self, btx):
        assert hasattr(btx.fs.unit, "inlet_1")
        assert len(btx.fs.unit.inlet_1.vars) == 4
        assert hasattr(btx.fs.unit.inlet_1, "flow_mol")
        assert hasattr(btx.fs.unit.inlet_1, "mole_frac_comp")
        assert hasattr(btx.fs.unit.inlet_1, "temperature")
        assert hasattr(btx.fs.unit.inlet_1, "pressure")

        assert hasattr(btx.fs.unit, "inlet_2")
        assert len(btx.fs.unit.inlet_2.vars) == 4
        assert hasattr(btx.fs.unit.inlet_2, "flow_mol")
        assert hasattr(btx.fs.unit.inlet_2, "mole_frac_comp")
        assert hasattr(btx.fs.unit.inlet_2, "temperature")
        assert hasattr(btx.fs.unit.inlet_2, "pressure")

        assert hasattr(btx.fs.unit, "outlet")
        assert len(btx.fs.unit.outlet.vars) == 4
        assert hasattr(btx.fs.unit.outlet, "flow_mol")
        assert hasattr(btx.fs.unit.outlet, "mole_frac_comp")
        assert hasattr(btx.fs.unit.outlet, "temperature")
        assert hasattr(btx.fs.unit.outlet, "pressure")

        assert number_variables(btx) == 35
        assert number_total_constraints(btx) == 25
        assert number_unused_variables(btx) == 0

    @pytest.mark.component
    def test_units(self, btx):
        assert_units_consistent(btx)

    @pytest.mark.unit
    def test_dof(self, btx):
        assert degrees_of_freedom(btx) == 0

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_initialize(self, btx):
        initialization_tester(btx)

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_solve(self, btx):
        results = solver.solve(btx)

        # Check for optimal solution
        assert results.solver.termination_condition == \
            TerminationCondition.optimal
        assert results.solver.status == SolverStatus.ok

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_solution(self, btx):
        assert (pytest.approx(6, abs=1e-3) ==
                value(btx.fs.unit.outlet.flow_mol[0]))
        assert (pytest.approx(354.7, abs=1e-1) ==
                value(btx.fs.unit.outlet.temperature[0]))
        assert (pytest.approx(101325, abs=1e3) ==
                value(btx.fs.unit.outlet.pressure[0]))

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_conservation(self, btx):
        assert abs(value(btx.fs.unit.inlet_1.flow_mol[0] +
                         btx.fs.unit.inlet_2.flow_mol[0] -
                         btx.fs.unit.outlet.flow_mol[0])) <= 1e-6

        assert 1e-6 >= abs(value(
                btx.fs.unit.inlet_1.flow_mol[0] *
                btx.fs.unit.inlet_1_state[0].enth_mol_phase['Liq'] +
                btx.fs.unit.inlet_2.flow_mol[0] *
                btx.fs.unit.inlet_2_state[0].enth_mol_phase['Liq'] -
                btx.fs.unit.outlet.flow_mol[0] *
                btx.fs.unit.mixed_state[0].enth_mol_phase['Liq']))

    @pytest.mark.ui
    @pytest.mark.unit
    def test_report(self, btx):
        btx.fs.unit.report()


# -----------------------------------------------------------------------------
# Tests for Mixer in cases where proeprties do not support pressure
@declare_process_block_class("NoPressureTestBlock")
class _NoPressureParameterBlock(PhysicalParameterBlock):
    def build(self):
        super(_NoPressureParameterBlock, self).build()

        self.phase_list = Set(initialize=["p1", "p2"])
        self.component_list = Set(initialize=["c1", "c2"])
        self.phase_equilibrium_idx = Set(initialize=["e1", "e2"])

        self.phase_equilibrium_list = \
            {"e1": ["c1", ("p1", "p2")],
             "e2": ["c2", ("p1", "p2")]}

        self.state_block_class = NoPressureStateBlock

    @classmethod
    def define_metadata(cls, obj):
        obj.add_default_units({'time': 's',
                               'length': 'm',
                               'mass': 'g',
                               'amount': 'mol',
                               'temperature': 'K',
                               'energy': 'J',
                               'holdup': 'mol'})


@declare_process_block_class("NoPressureStateBlock",
                             block_class=StateBlock)
class NoPressureStateBlockData(StateBlockData):
    CONFIG = ConfigBlock(implicit=True)

    def build(self):
        super(NoPressureStateBlockData, self).build()

        self.flow_vol = Var(initialize=20)
        self.flow_mol_phase_comp = Var(self._params.phase_list,
                                       self._params.component_list,
                                       initialize=2)
        self.temperature = Var(initialize=300)
        self.test_var = Var(initialize=1)

    def get_material_flow_terms(b, p, j):
        return b.test_var

    def get_enthalpy_flow_terms(b, p):
        return b.test_var

    def default_material_balance_type(self):
        return MaterialBalanceType.componentPhase

    def default_energy_balance_type(self):
        return EnergyBalanceType.enthalpyTotal


class TestMixer_NoPressure(object):
    @declare_process_block_class("MixerFrame")
    class MixerFrameData(MixerData):
        def build(self):
            super(MixerData, self).build()

    @pytest.mark.build
    @pytest.mark.unit
    def test_pressure_minimization_unsupported(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = NoPressureTestBlock()

        with pytest.raises(
                PropertyNotSupportedError,
                match="fs.mix The property package supplied for this unit "
                "does not appear to support pressure, which is required for "
                "momentum mixing. Please set momentum_mixing_type to "
                "MomentumMixingType.none or provide a property package which "
                "supports pressure."):
            m.fs.mix = Mixer(default={
                "property_package": m.fs.pp,
                "momentum_mixing_type": MomentumMixingType.minimize,
                "construct_ports": False})

    @pytest.mark.build
    @pytest.mark.unit
    def test_pressure_equal_unsupported(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = NoPressureTestBlock()

        with pytest.raises(
                PropertyNotSupportedError,
                match="fs.mix The property package supplied for this unit "
                "does not appear to support pressure, which is required for "
                "momentum mixing. Please set momentum_mixing_type to "
                "MomentumMixingType.none or provide a property package which "
                "supports pressure."):
            m.fs.mix = Mixer(default={
                "property_package": m.fs.pp,
                "momentum_mixing_type": MomentumMixingType.equality,
                "construct_ports": False})

    @pytest.mark.build
    @pytest.mark.unit
    def test_pressure_equal_and_min_unsupported(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = NoPressureTestBlock()

        with pytest.raises(
                PropertyNotSupportedError,
                match="fs.mix The property package supplied for this unit "
                "does not appear to support pressure, which is required for "
                "momentum mixing. Please set momentum_mixing_type to "
                "MomentumMixingType.none or provide a property package which "
                "supports pressure."):
            m.fs.mix = Mixer(default={
                "property_package": m.fs.pp,
                "momentum_mixing_type":
                    MomentumMixingType.minimize_and_equality,
                "construct_ports": False})

    @pytest.mark.build
    @pytest.mark.unit
    def test_pressure_none_unsupported(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.pp = NoPressureTestBlock()

        m.fs.mix = Mixer(default={
            "property_package": m.fs.pp,
            "momentum_mixing_type": MomentumMixingType.none,
            "construct_ports": False})

# -----------------------------------------------------------------------------
@pytest.mark.iapws
@pytest.mark.skipif(not iapws95.iapws95_available(),
                    reason="IAPWS not available")
class TestIAPWS(object):
    @pytest.fixture(scope="class")
    def iapws(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})

        m.fs.properties = iapws95.Iapws95ParameterBlock()

        m.fs.unit = Mixer(default={
                "property_package": m.fs.properties,
                "material_balance_type": MaterialBalanceType.componentTotal,
                "momentum_mixing_type": MomentumMixingType.equality})

        m.fs.unit.inlet_1.flow_mol[0].fix(100)
        m.fs.unit.inlet_1.enth_mol[0].fix(4000)
        m.fs.unit.inlet_1.pressure[0].fix(101325)

        m.fs.unit.inlet_2.flow_mol[0].fix(100)
        m.fs.unit.inlet_2.enth_mol[0].fix(3500)

        return m

    @pytest.mark.build
    @pytest.mark.unit
    def test_build(self, iapws):
        assert len(iapws.fs.unit.inlet_1.vars) == 3
        assert hasattr(iapws.fs.unit.inlet_1, "flow_mol")
        assert hasattr(iapws.fs.unit.inlet_1, "enth_mol")
        assert hasattr(iapws.fs.unit.inlet_1, "pressure")

        assert len(iapws.fs.unit.inlet_2.vars) == 3
        assert hasattr(iapws.fs.unit.inlet_2, "flow_mol")
        assert hasattr(iapws.fs.unit.inlet_2, "enth_mol")
        assert hasattr(iapws.fs.unit.inlet_2, "pressure")

        assert hasattr(iapws.fs.unit, "outlet")
        assert len(iapws.fs.unit.outlet.vars) == 3
        assert hasattr(iapws.fs.unit.outlet, "flow_mol")
        assert hasattr(iapws.fs.unit.outlet, "enth_mol")
        assert hasattr(iapws.fs.unit.outlet, "pressure")

    @pytest.mark.component
    def test_units(self, iapws):
        assert_units_consistent(iapws)

    @pytest.mark.unit
    def test_dof(self, iapws):
        assert degrees_of_freedom(iapws) == 0

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_initialize(self, iapws):
        initialization_tester(iapws)

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_solve(self, iapws):
        results = solver.solve(iapws)

        # Check for optimal solution
        assert results.solver.termination_condition == \
            TerminationCondition.optimal
        assert results.solver.status == SolverStatus.ok

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_solution(self, iapws):
        assert pytest.approx(200, abs=1e-5) == \
            value(iapws.fs.unit.outlet.flow_mol[0])

        assert pytest.approx(3750, abs=1e0) == \
            value(iapws.fs.unit.outlet.enth_mol[0])

        assert pytest.approx(101325, abs=1e2) == \
            value(iapws.fs.unit.outlet.pressure[0])

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_conservation(self, iapws):
        assert abs(value(iapws.fs.unit.inlet_1.flow_mol[0] +
                         iapws.fs.unit.inlet_2.flow_mol[0] -
                         iapws.fs.unit.outlet.flow_mol[0])) <= 1e-6

        assert abs(value(iapws.fs.unit.inlet_1.flow_mol[0] *
                         iapws.fs.unit.inlet_1.enth_mol[0] +
                         iapws.fs.unit.inlet_2.flow_mol[0] *
                         iapws.fs.unit.inlet_2.enth_mol[0] -
                         iapws.fs.unit.outlet.flow_mol[0] *
                         iapws.fs.unit.outlet.enth_mol[0])) <= 1e-6

    @pytest.mark.ui
    @pytest.mark.unit
    def test_report(self, iapws):
        iapws.fs.unit.report()


# -----------------------------------------------------------------------------
class TestSaponification(object):
    @pytest.fixture(scope="class")
    def sapon(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})

        m.fs.properties = SaponificationParameterBlock()

        m.fs.unit = Mixer(default={"property_package": m.fs.properties})

        m.fs.unit.inlet_1.flow_vol[0].fix(1e-3)
        m.fs.unit.inlet_1.temperature[0].fix(320)
        m.fs.unit.inlet_1.pressure[0].fix(101325)
        m.fs.unit.inlet_1.conc_mol_comp[0, "H2O"].fix(55388.0)
        m.fs.unit.inlet_1.conc_mol_comp[0, "NaOH"].fix(100.0)
        m.fs.unit.inlet_1.conc_mol_comp[0, "EthylAcetate"].fix(100.0)
        m.fs.unit.inlet_1.conc_mol_comp[0, "SodiumAcetate"].fix(0.0)
        m.fs.unit.inlet_1.conc_mol_comp[0, "Ethanol"].fix(0.0)

        m.fs.unit.inlet_2.flow_vol[0].fix(1e-3)
        m.fs.unit.inlet_2.temperature[0].fix(300)
        m.fs.unit.inlet_2.pressure[0].fix(101325)
        m.fs.unit.inlet_2.conc_mol_comp[0, "H2O"].fix(55388.0)
        m.fs.unit.inlet_2.conc_mol_comp[0, "NaOH"].fix(100.0)
        m.fs.unit.inlet_2.conc_mol_comp[0, "EthylAcetate"].fix(100.0)
        m.fs.unit.inlet_2.conc_mol_comp[0, "SodiumAcetate"].fix(0.0)
        m.fs.unit.inlet_2.conc_mol_comp[0, "Ethanol"].fix(0.0)

        return m

    @pytest.mark.build
    @pytest.mark.unit
    def test_build(self, sapon):
        assert len(sapon.fs.unit.inlet_1.vars) == 4
        assert hasattr(sapon.fs.unit.inlet_1, "flow_vol")
        assert hasattr(sapon.fs.unit.inlet_1, "conc_mol_comp")
        assert hasattr(sapon.fs.unit.inlet_1, "temperature")
        assert hasattr(sapon.fs.unit.inlet_1, "pressure")

        assert len(sapon.fs.unit.inlet_2.vars) == 4
        assert hasattr(sapon.fs.unit.inlet_2, "flow_vol")
        assert hasattr(sapon.fs.unit.inlet_2, "conc_mol_comp")
        assert hasattr(sapon.fs.unit.inlet_2, "temperature")
        assert hasattr(sapon.fs.unit.inlet_2, "pressure")

        assert len(sapon.fs.unit.outlet.vars) == 4
        assert hasattr(sapon.fs.unit.outlet, "flow_vol")
        assert hasattr(sapon.fs.unit.outlet, "conc_mol_comp")
        assert hasattr(sapon.fs.unit.outlet, "temperature")
        assert hasattr(sapon.fs.unit.outlet, "pressure")

        assert number_variables(sapon) == 26
        assert number_total_constraints(sapon) == 10
        assert number_unused_variables(sapon) == 0

    @pytest.mark.component
    def test_units(self, sapon):
        assert_units_consistent(sapon)

    @pytest.mark.unit
    def test_dof(self, sapon):
        assert degrees_of_freedom(sapon) == 0

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_initialize(self, sapon):
        initialization_tester(sapon)

    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_solve(self, sapon):
        results = solver.solve(sapon)

        # Check for optimal solution
        assert results.solver.termination_condition == \
            TerminationCondition.optimal
        assert results.solver.status == SolverStatus.ok

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_solution(self, sapon):
        assert pytest.approx(2e-3, abs=1e-6) == \
            value(sapon.fs.unit.outlet.flow_vol[0])

        assert pytest.approx(55388.0, abs=1e0) == value(
                sapon.fs.unit.outlet.conc_mol_comp[0, "H2O"])
        assert pytest.approx(100.0, abs=1e-3) == value(
                sapon.fs.unit.outlet.conc_mol_comp[0, "NaOH"])
        assert pytest.approx(100.0, abs=1e-3) == value(
                sapon.fs.unit.outlet.conc_mol_comp[0, "EthylAcetate"])
        assert pytest.approx(0.0, abs=1e-3) == value(
                sapon.fs.unit.outlet.conc_mol_comp[0, "SodiumAcetate"])
        assert pytest.approx(0.0, abs=1e-3) == value(
                sapon.fs.unit.outlet.conc_mol_comp[0, "Ethanol"])

        assert pytest.approx(310.0, abs=1e-1) == \
            value(sapon.fs.unit.outlet.temperature[0])

        assert pytest.approx(101325, abs=1e2) == \
            value(sapon.fs.unit.outlet.pressure[0])

    @pytest.mark.solver
    @pytest.mark.skipif(solver is None, reason="Solver not available")
    @pytest.mark.component
    def test_conservation(self, sapon):
        assert abs(value(sapon.fs.unit.inlet_1.flow_vol[0] +
                         sapon.fs.unit.inlet_2.flow_vol[0] -
                         sapon.fs.unit.outlet.flow_vol[0])) <= 1e-6

        assert abs(value(
                sapon.fs.unit.inlet_1.flow_vol[0] *
                sapon.fs.properties.dens_mol*sapon.fs.properties.cp_mol *
                (sapon.fs.unit.inlet_1.temperature[0] -
                 sapon.fs.properties.temperature_ref) +
                sapon.fs.unit.inlet_2.flow_vol[0] *
                sapon.fs.properties.dens_mol*sapon.fs.properties.cp_mol *
                (sapon.fs.unit.inlet_2.temperature[0] -
                 sapon.fs.properties.temperature_ref) -
                sapon.fs.unit.outlet.flow_vol[0] *
                sapon.fs.properties.dens_mol*sapon.fs.properties.cp_mol *
                (sapon.fs.unit.outlet.temperature[0] -
                 sapon.fs.properties.temperature_ref))) <= 1e-3

    @pytest.mark.ui
    @pytest.mark.unit
    def test_report(self, sapon):
        sapon.fs.unit.report()
