Beispiel #1
0
def NRTL_model(data):
    """This function generates an instance of the NRTL Pyomo model using 'data' as the input argument
    
    Parameters
    ----------
    data: pandas DataFrame, list of dictionaries, or list of json file names
        Data that is used to build an instance of the Pyomo model
    
    Returns
    -------
    m: an instance of the Pyomo model
        for estimating parameters and covariance
    """
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.properties = BTXParameterBlock(default={
        "valid_phase": ('Liq', 'Vap'),
        "activity_coeff_model": 'NRTL'
    })
    m.fs.flash = Flash(default={"property_package": m.fs.properties})

    # Initialize at a certain inlet condition
    m.fs.flash.inlet.flow_mol.fix(1)
    m.fs.flash.inlet.temperature.fix(368)
    m.fs.flash.inlet.pressure.fix(101325)
    m.fs.flash.inlet.mole_frac_comp[0, "benzene"].fix(0.5)
    m.fs.flash.inlet.mole_frac_comp[0, "toluene"].fix(0.5)

    # Set Flash unit specifications
    m.fs.flash.heat_duty.fix(0)
    m.fs.flash.deltaP.fix(0)

    # Fix NRTL specific variables
    # alpha values (set at 0.3)
    m.fs.properties.alpha["benzene", "benzene"].fix(0)
    m.fs.properties.alpha["benzene", "toluene"].fix(0.3)
    m.fs.properties.alpha["toluene", "toluene"].fix(0)
    m.fs.properties.alpha["toluene", "benzene"].fix(0.3)

    # initial tau values
    m.fs.properties.tau["benzene", "benzene"].fix(0)
    m.fs.properties.tau["benzene", "toluene"].fix(0.1690)
    m.fs.properties.tau["toluene", "toluene"].fix(0)
    m.fs.properties.tau["toluene", "benzene"].fix(-0.1559)

    # Initialize the flash unit
    m.fs.flash.initialize(outlvl=idaeslog.INFO_LOW)

    # Fix at actual temperature
    m.fs.flash.inlet.temperature.fix(float(data["temperature"]))

    # Set bounds on variables to be estimated
    m.fs.properties.tau["benzene", "toluene"].setlb(-5)
    m.fs.properties.tau["benzene", "toluene"].setub(5)

    m.fs.properties.tau["toluene", "benzene"].setlb(-5)
    m.fs.properties.tau["toluene", "benzene"].setub(5)

    # Return initialized flash model
    return m
Beispiel #2
0
    def model(self):
        model = ConcreteModel()

        model.fs = FlowsheetBlock(default={"dynamic": False})

        model.fs.param = GenericParameterBlock(default=configuration)

        model.fs.unit = Flash(
            default={
                "property_package": model.fs.param,
                "has_heat_transfer": False,
                "has_pressure_change": False
            })
        # Fix state
        model.fs.unit.inlet.flow_mol.fix(1)
        model.fs.unit.inlet.temperature.fix(200.00)
        model.fs.unit.inlet.pressure.fix(101325)
        model.fs.unit.inlet.mole_frac_comp[0, "carbon_dioxide"].fix(1 / 2)
        model.fs.unit.inlet.mole_frac_comp[0, "bmimPF6"].fix(1 / 2)

        assert degrees_of_freedom(model.fs) == 0

        # Apply scaling - model will not solver without this
        model.fs.unit.control_volume.properties_in[
            0].calculate_scaling_factors()
        model.fs.unit.control_volume.properties_out[
            0].calculate_scaling_factors()

        return model
Beispiel #3
0
def model():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": True})

    m.fs.params = PropertyInterrogatorBlock()

    m.fs.P01 = PressureChanger(
        default={
            "property_package": m.fs.params,
            "thermodynamic_assumption": ThermodynamicAssumption.isentropic
        })

    m.fs.HX02 = HeatExchanger1D(
        default={
            "shell_side": {
                "property_package": m.fs.params
            },
            "tube_side": {
                "property_package": m.fs.params
            }
        })

    m.fs.F03 = Flash(default={"property_package": m.fs.params})

    return m
Beispiel #4
0
def NRTL_model_opt():
    """This function generates an instance of the NRTL Pyomo model
    
    Returns
    -------
    m: an instance of the Pyomo model
        for uncertainty propagation
    """
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.properties = BTXParameterBlock(default={"valid_phase":
                                                 ('Liq', 'Vap'),
                                                 "activity_coeff_model":
                                                 'NRTL'})
    m.fs.flash = Flash(default={"property_package": m.fs.properties})

    # Initialize at a certain inlet condition
    m.fs.flash.inlet.flow_mol.fix(1)
    m.fs.flash.inlet.temperature.fix(368)
    m.fs.flash.inlet.pressure.fix(101325)
    m.fs.flash.inlet.mole_frac_comp[0, "benzene"].fix(0.5)
    m.fs.flash.inlet.mole_frac_comp[0, "toluene"].fix(0.5)

    # Set Flash unit specifications
    m.fs.flash.heat_duty.fix(0)
    m.fs.flash.deltaP.fix(0)

    # Fix NRTL specific variables
    # alpha values (set at 0.3)
    m.fs.properties.alpha["benzene", "benzene"].fix(0)
    m.fs.properties.alpha["benzene", "toluene"].fix(0.3)
    m.fs.properties.alpha["toluene", "toluene"].fix(0)
    m.fs.properties.alpha["toluene", "benzene"].fix(0.3)

    # initial tau values
    m.fs.properties.tau["benzene", "benzene"].fix(0)
    m.fs.properties.tau["benzene", "toluene"].fix(0.1690)
    m.fs.properties.tau["toluene", "toluene"].fix(0)
    m.fs.properties.tau["toluene", "benzene"].fix(-0.1559)

    # Initialize the flash unit
    m.fs.flash.initialize(outlvl=idaeslog.INFO_LOW)

    # Fix at actual temperature
    m.fs.flash.inlet.temperature.fix(float(368))

    # Set bounds on variables to be estimated
    m.fs.properties.tau["benzene", "toluene"].setlb(-5)
    m.fs.properties.tau["benzene", "toluene"].setub(5)

    m.fs.properties.tau["toluene", "benzene"].setlb(-5)
    m.fs.properties.tau["toluene", "benzene"].setub(5)
    
    # To use kaug
    # objective function required
    # need to unfix the variables
    m.obj = Objective(expr = 0*m.fs.properties.tau["benzene","toluene"] + exp(-m.fs.properties.alpha['toluene','benzene'].value * m.fs.properties.tau['toluene','benzene']), sense=minimize)
    m.fs.properties.tau["benzene", "toluene"].fixed = False # To use kaug
    m.fs.properties.tau["toluene", "benzene"].fixed = False
    return m
Beispiel #5
0
def flash_model():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.properties = PhysicalParameterTestBlock()#default={"valid_phase": 
                                                 # ('Liq', 'Vap')})
    m.fs.flash = Flash(default={"property_package": m.fs.properties})
    
    return m
    def test_unit_dof(self, model):
        model.fs.unit = Flash(
            default={
                "property_package": model.fs.param,
                "has_heat_transfer": False,
                "has_pressure_change": False
            })
        # Fix state
        model.fs.unit.inlet.flow_mol.fix(1)
        model.fs.unit.inlet.temperature.fix(200.00)
        model.fs.unit.inlet.pressure.fix(101325)
        model.fs.unit.inlet.mole_frac_comp[0, "carbon_dioxide"].fix(1 / 2)
        model.fs.unit.inlet.mole_frac_comp[0, "bmimPF6"].fix(1 / 2)

        assert degrees_of_freedom(model.fs.unit) == 0
Beispiel #7
0
def demo_flowsheet():
    """Semi-complicated demonstration flowsheet.
    """
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.BT_props = BTXParameterBlock()
    m.fs.M01 = Mixer(default={"property_package": m.fs.BT_props})
    m.fs.H02 = Heater(default={"property_package": m.fs.BT_props})
    m.fs.F03 = Flash(default={"property_package": m.fs.BT_props})
    m.fs.s01 = Arc(source=m.fs.M01.outlet, destination=m.fs.H02.inlet)
    m.fs.s02 = Arc(source=m.fs.H02.outlet, destination=m.fs.F03.inlet)
    TransformationFactory("network.expand_arcs").apply_to(m.fs)

    m.fs.properties = SWCO2ParameterBlock()
    m.fs.main_compressor = PressureChanger(
      default={'dynamic': False,
               'property_package': m.fs.properties,
               'compressor': True,
               'thermodynamic_assumption': ThermodynamicAssumption.isentropic})

    m.fs.bypass_compressor = PressureChanger(
        default={'dynamic': False,
                 'property_package': m.fs.properties,
                 'compressor': True,
                 'thermodynamic_assumption': ThermodynamicAssumption.isentropic})

    m.fs.turbine = PressureChanger(
      default={'dynamic': False,
               'property_package': m.fs.properties,
               'compressor': False,
               'thermodynamic_assumption': ThermodynamicAssumption.isentropic})
    m.fs.boiler = Heater(default={'dynamic': False,
                                  'property_package': m.fs.properties,
                                  'has_pressure_change': True})
    m.fs.FG_cooler = Heater(default={'dynamic': False,
                                     'property_package': m.fs.properties,
                                     'has_pressure_change': True})
    m.fs.pre_boiler = Heater(default={'dynamic': False,
                                      'property_package': m.fs.properties,
                                      'has_pressure_change': False})
    m.fs.HTR_pseudo_tube = Heater(default={'dynamic': False,
                                       'property_package': m.fs.properties,
                                       'has_pressure_change': True})
    m.fs.LTR_pseudo_tube = Heater(default={'dynamic': False,
                                       'property_package': m.fs.properties,
                                       'has_pressure_change': True})
    return m.fs
Beispiel #8
0
def flash_flowsheet():
    # Model and flowsheet
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    # Flash properties
    m.fs.properties = BTXParameterBlock(default={"valid_phase": ('Liq', 'Vap'),
                                                 "activity_coeff_model": "Ideal",
                                                 "state_vars": "FTPz"})
    # Flash unit
    m.fs.flash = Flash(default={"property_package": m.fs.properties})
    m.fs.flash.inlet.flow_mol.fix(1)
    m.fs.flash.inlet.temperature.fix(368)
    m.fs.flash.inlet.pressure.fix(101325)
    m.fs.flash.inlet.mole_frac_comp[0, "benzene"].fix(0.5)
    m.fs.flash.inlet.mole_frac_comp[0, "toluene"].fix(0.5)
    m.fs.flash.heat_duty.fix(0)
    m.fs.flash.deltaP.fix(0)
    return m.fs
def build_flowsheet():
    m = ConcreteModel()

    m.fs = FlowsheetBlock(default={"dynamic": False})

    m.fs.BT_props = BTXParameterBlock()

    m.fs.M01 = Mixer(default={"property_package": m.fs.BT_props})

    m.fs.H02 = Heater(default={"property_package": m.fs.BT_props})

    m.fs.F03 = Flash(default={"property_package": m.fs.BT_props})

    m.fs.s01 = Arc(source=m.fs.M01.outlet, destination=m.fs.H02.inlet)
    m.fs.s02 = Arc(source=m.fs.H02.outlet, destination=m.fs.F03.inlet)

    TransformationFactory("network.expand_arcs").apply_to(m.fs)

    return m
Beispiel #10
0
def flash_model():
    """Flash unit model. Use '.fs' attribute to get the flowsheet."""
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    # Flash properties
    m.fs.properties = BTXParameterBlock(
        default={
            "valid_phase": ("Liq", "Vap"),
            "activity_coeff_model": "Ideal",
            "state_vars": "FTPz",
        })
    # Flash unit
    m.fs.flash = Flash(default={"property_package": m.fs.properties})
    m.fs.flash.inlet.flow_mol.fix(1)
    m.fs.flash.inlet.temperature.fix(368)
    m.fs.flash.inlet.pressure.fix(101325)
    m.fs.flash.inlet.mole_frac_comp[0, "benzene"].fix(0.5)
    m.fs.flash.inlet.mole_frac_comp[0, "toluene"].fix(0.5)
    m.fs.flash.heat_duty.fix(0)
    m.fs.flash.deltaP.fix(0)
    return m
Beispiel #11
0
def test_serialize_flowsheet():
    # Construct the model from idaes/examples/workshops/Module_2_Flowsheet/Module_2_Flowsheet_Solution.ipynb
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.thermo_params = thermo_props.HDAParameterBlock()
    m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(
        default={"property_package": m.fs.thermo_params})

    m.fs.M101 = Mixer(
        default={
            "property_package": m.fs.thermo_params,
            "inlet_list": ["toluene_feed", "hydrogen_feed", "vapor_recycle"]
        })

    m.fs.H101 = Heater(
        default={
            "property_package": m.fs.thermo_params,
            "has_pressure_change": False,
            "has_phase_equilibrium": True
        })
    m.fs.R101 = StoichiometricReactor(
        default={
            "property_package": m.fs.thermo_params,
            "reaction_package": m.fs.reaction_params,
            "has_heat_of_reaction": True,
            "has_heat_transfer": True,
            "has_pressure_change": False
        })
    m.fs.F101 = Flash(
        default={
            "property_package": m.fs.thermo_params,
            "has_heat_transfer": True,
            "has_pressure_change": True
        })
    m.fs.S101 = Splitter(
        default={
            "property_package": m.fs.thermo_params,
            "ideal_separation": False,
            "outlet_list": ["purge", "recycle"]
        })
    m.fs.C101 = PressureChanger(
        default={
            "property_package": m.fs.thermo_params,
            "compressor": True,
            "thermodynamic_assumption": ThermodynamicAssumption.isothermal
        })
    m.fs.F102 = Flash(
        default={
            "property_package": m.fs.thermo_params,
            "has_heat_transfer": True,
            "has_pressure_change": True
        })

    m.fs.s03 = Arc(source=m.fs.M101.outlet, destination=m.fs.H101.inlet)
    m.fs.s04 = Arc(source=m.fs.H101.outlet, destination=m.fs.R101.inlet)
    m.fs.s05 = Arc(source=m.fs.R101.outlet, destination=m.fs.F101.inlet)
    m.fs.s06 = Arc(source=m.fs.F101.vap_outlet, destination=m.fs.S101.inlet)
    m.fs.s08 = Arc(source=m.fs.S101.recycle, destination=m.fs.C101.inlet)
    m.fs.s09 = Arc(source=m.fs.C101.outlet,
                   destination=m.fs.M101.vapor_recycle)
    m.fs.s10 = Arc(source=m.fs.F101.liq_outlet, destination=m.fs.F102.inlet)

    fss = FlowsheetSerializer()
    fss.serialize_flowsheet(m.fs)

    unit_models = fss.get_unit_models()
    unit_model_names_types = []
    for unit_model in unit_models:
        unit_model_names_types.append(unit_models[unit_model])

    unit_models_names_type_truth = [{
        'name': 'M101',
        'type': 'mixer'
    }, {
        'name': 'H101',
        'type': 'heater'
    }, {
        'name': 'R101',
        'type': 'stoichiometric_reactor'
    }, {
        'name': 'F101',
        'type': 'flash'
    }, {
        'name': 'S101',
        'type': 'separator'
    }, {
        'name': 'C101',
        'type': 'pressure_changer'
    }, {
        'name': 'F102',
        'type': 'flash'
    }]

    set_result = set(tuple(sorted(d.items())) for d in unit_model_names_types)
    set_truth = set(
        tuple(sorted(d.items())) for d in unit_models_names_type_truth)
    difference = list(set_truth.symmetric_difference(set_result))

    assert len(difference) == 0

    # TODO Figure out how to test ports. Maybe find out if we can find the parent component for the port?
    # ports = fss.get_ports()
    # assert ports == {"<pyomo.network.port.SimplePort object at 0x7fe8d0d79278>": "<idaes.core.process_block._ScalarMixer object at 0x7fe8d0d60360>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0d792e8>": "<idaes.core.process_block._ScalarMixer object at 0x7fe8d0d60360>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0d79358>": "<idaes.core.process_block._ScalarMixer object at 0x7fe8d0d60360>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0d793c8>": "<idaes.core.process_block._ScalarMixer object at 0x7fe8d0d60360>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0d797b8>": "<idaes.core.process_block._ScalarHeater object at 0x7fe8d0db74c8>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0d79828>": "<idaes.core.process_block._ScalarHeater object at 0x7fe8d0db74c8>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0d79a58>": "<idaes.core.process_block._ScalarStoichiometricReactor object at 0x7fe8d0de2ab0>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0d79ac8>": "<idaes.core.process_block._ScalarStoichiometricReactor object at 0x7fe8d0de2ab0>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0d79eb8>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8d0e0fdc8>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41128>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8d0e0fdc8>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41198>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8d0e0fdc8>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0d79f98>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8d0e0fdc8>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41048>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8d0e0fdc8>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e410b8>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8d0e0fdc8>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41278>": "<idaes.core.process_block._ScalarSeparator object at 0x7fe8d0e45708>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41588>": "<idaes.core.process_block._ScalarSeparator object at 0x7fe8d0e45708>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e415f8>": "<idaes.core.process_block._ScalarSeparator object at 0x7fe8d0e45708>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41828>": "<idaes.core.process_block._ScalarPressureChanger object at 0x7fe8d0e686c0>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41898>": "<idaes.core.process_block._ScalarPressureChanger object at 0x7fe8d0e686c0>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41c88>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8e1405cf0>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41eb8>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8e1405cf0>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41f28>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8e1405cf0>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41e48>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8e1405cf0>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41dd8>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8e1405cf0>",
    #                  "<pyomo.network.port.SimplePort object at 0x7fe8d0e41d68>": "<idaes.core.process_block._ScalarFlash object at 0x7fe8e1405cf0>"
    #                  }

    named_edges_results = {}
    edges = fss.get_edges()
    for edge in edges:
        named_edges_results[edge.getname()] = [
            x.getname() for x in edges[edge]
        ]

    named_edges_truth = {
        'M101': ['H101'],
        'H101': ['R101'],
        'R101': ['F101'],
        'F101': ['S101', 'F102'],
        'S101': ['C101'],
        'C101': ['M101']
    }

    assert named_edges_results == named_edges_truth
    def test_temp_swing(self):
        # Create a flash model with the CO2-H2O property package
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.properties = GenericParameterBlock(default=configuration)
        m.fs.flash = Flash(default={"property_package": m.fs.properties})

        # Fix inlet stream state variables
        m.fs.flash.inlet.flow_mol.fix(9.89433124673833)  # mol/s
        m.fs.flash.inlet.mole_frac_comp[0, 'CO2'].fix(0.13805801934749645)
        m.fs.flash.inlet.mole_frac_comp[0, 'H2O'].fix(0.8619419806525035)
        m.fs.flash.inlet.pressure.fix(183430)  # Pa
        m.fs.flash.inlet.temperature.fix(396.79057912844183)  # K

        # Fix flash and its outlet conditions
        m.fs.flash.deltaP.fix(0)
        m.fs.flash.vap_outlet.temperature.fix(313.15)

        # Initialize the flash model
        m.fs.flash.initialize()

        # Create a dictionary of expected solution for flash outlet temperature
        # sweep
        temp_range = list(np.linspace(313, 396))

        expected_vapor_frac = [
            0.14388, 0.14445, 0.14508, 0.14576, 0.1465, 0.14731, 0.14818,
            0.14913, 0.15017, 0.15129, 0.15251, 0.15384, 0.15528, 0.15685,
            0.15856, 0.16042, 0.16245, 0.16467, 0.16709, 0.16974, 0.17265,
            0.17584, 0.17935, 0.18323, 0.18751, 0.19226, 0.19755, 0.20346,
            0.21008, 0.21755, 0.22601, 0.23565, 0.24673, 0.25956, 0.27456,
            0.29229, 0.31354, 0.33942, 0.37157, 0.4125, 0.46628, 0.53993,
            0.64678, 0.81547, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
        ]

        expected_heat_duty = [
            -396276.55508, -394859.42896, -393421.28226, -391960.40602,
            -390474.94512, -388962.88119, -387422.01297, -385849.93371,
            -384244.00483, -382601.32549, -380918.69691, -379192.58068,
            -377419.04976, -375593.73054, -373711.73419, -371767.57480,
            -369755.07135, -367667.22968, -365496.09940, -363232.59958,
            -360866.30485, -358385.18097, -355775.25563, -353020.20490,
            -350100.82935, -346994.38367, -343673.70980, -340106.10300,
            -336251.80960, -332062.00898, -327476.06061, -322417.68382,
            -316789.55435, -310465.49473, -303278.90949, -295005.17406,
            -285333.93764, -273823.89005, -259825.51107, -242341.82570,
            -219760.16114, -189290.02362, -145647.55666, -77469.59283,
            -3219.88910, -2631.66067, -2043.09220, -1454.17760, -864.91585,
            -275.30623
        ]

        outvals = zip(expected_vapor_frac, expected_heat_duty)
        expected_sol = dict(zip(temp_range, outvals))

        # Solve the model for a range of flash outlet temperatures
        # Perform flash outlet temperature sweep and test the solution
        for t in temp_range:
            m.fs.flash.vap_outlet.temperature.fix(t)
            res = solver.solve(m)
            assert res.solver.termination_condition == "optimal"
            frac = value(m.fs.flash.vap_outlet.flow_mol[0]) \
                / value(m.fs.flash.inlet.flow_mol[0])
            assert frac == pytest.approx(expected_sol[t][0], abs=1e-4)
            hduty = value(m.fs.flash.heat_duty[0])
            assert hduty == pytest.approx(expected_sol[t][1], rel=1e-4)
    def test_temp_swing(self):
        # Create a flash model with the CO2-H2O property package
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})
        m.fs.properties = GenericParameterBlock(default=configuration)
        m.fs.flash = Flash(default={"property_package": m.fs.properties})

        # Fix inlet stream state variables
        m.fs.flash.inlet.flow_mol.fix(9.89433124673833)  # mol/s
        m.fs.flash.inlet.mole_frac_comp[0, 'CO2'].fix(0.13805801934749645)
        m.fs.flash.inlet.mole_frac_comp[0, 'H2O'].fix(0.8619419806525035)
        m.fs.flash.inlet.pressure.fix(183430)  # Pa
        m.fs.flash.inlet.temperature.fix(396.79057912844183)  # K

        # Fix flash and its outlet conditions
        m.fs.flash.deltaP.fix(0)
        m.fs.flash.vap_outlet.temperature.fix(313.15)

        #Initialize the flash model
        initial = m.fs.flash.initialize(outlvl=0)

        # Create a dictionary of expected solution for flash outlet temperature
        #sweep
        temp_range = list(np.linspace(313, 396))

        expected_vapor_frac = [
            0.14388, 0.14445, 0.14508, 0.14576, 0.1465, 0.14731, 0.14818,
            0.14913, 0.15017, 0.15129, 0.15251, 0.15384, 0.15528, 0.15685,
            0.15856, 0.16042, 0.16245, 0.16467, 0.16709, 0.16974, 0.17265,
            0.17584, 0.17935, 0.18323, 0.18751, 0.19226, 0.19755, 0.20346,
            0.21008, 0.21755, 0.22601, 0.23565, 0.24673, 0.25956, 0.27456,
            0.29229, 0.31354, 0.33942, 0.37157, 0.4125, 0.46628, 0.53993,
            0.64678, 0.81547, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
        ]

        expected_heat_duty = [
            -319572.91269, -318207.35249, -316825.31456, -315425.46452,
            -314006.35514, -312566.41317, -311103.92414, -309617.01485,
            -308103.633, -306561.52359, -304988.20138, -303380.91855,
            -301736.62677, -300051.9324, -298323.04327, -296545.70535,
            -294715.12686, -292825.88683, -290871.8244, -288845.90382,
            -286740.04885, -284544.93807, -282249.74995, -279841.84278,
            -277306.349, -274625.65625, -271778.73623, -268740.26687,
            -265479.46928, -261958.54546, -258130.54709, -253936.4179,
            -249300.81058, -244126.04073, -238283.13381, -231598.19109,
            -223830.94744, -214639.75318, -203521.76902, -189705.16711,
            -171941.46564, -148070.35054, -114001.22402, -60937.07508,
            -3219.88715, -2631.66029, -2043.09203, -1454.17752, -864.91582,
            -275.30623
        ]

        outvals = zip(expected_vapor_frac, expected_heat_duty)
        expected_sol = dict(zip(temp_range, outvals))

        # Solve the model for a range of flash outlet temperatures
        # Perform flash outlet temperature sweep and test the solution
        for t in temp_range:
            m.fs.flash.vap_outlet.temperature.fix(t)
            res = solver.solve(m)
            assert res.solver.termination_condition == "optimal"
            frac = value(m.fs.flash.vap_outlet.flow_mol[0]) \
                /value(m.fs.flash.inlet.flow_mol[0])
            assert frac == pytest.approx(expected_sol[t][0], abs=1e-4)
            hduty = value(m.fs.flash.heat_duty[0])
            assert hduty == pytest.approx(expected_sol[t][1], abs=1e-4)