Example #1
0
def tu(delta_temperature_callback=delta_temperature_underwood_tune_callback):
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})

    m.fs.properties = PhysicalParameterTestBlock()
    m.fs.prop_steam = iapws95.Iapws95ParameterBlock()
    m.fs.prop_fluegas = FlueGasParameterBlock()

    m.fs.unit = BoilerHeatExchanger(
        default={
            "delta_temperature_callback": delta_temperature_callback,
            "tube": {
                "property_package": m.fs.prop_steam
            },
            "shell": {
                "property_package": m.fs.prop_fluegas
            },
            "has_pressure_change": True,
            "has_holdup": False,
            "flow_pattern": HeatExchangerFlowPattern.countercurrent,
            "tube_arrangement": TubeArrangement.inLine,
            "side_1_water_phase": "Liq",
            "has_radiation": True,
        })

    assert_units_consistent(m)
def test_config():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})

    m.fs.properties = PhysicalParameterTestBlock()
    m.fs.prop_steam = iapws95.Iapws95ParameterBlock()
    m.fs.prop_fluegas = FlueGasParameterBlock()

    m.fs.unit = BoilerHeatExchanger(
        default={
            "side_1_property_package": m.fs.prop_steam,
            "side_2_property_package": m.fs.prop_fluegas,
            "has_pressure_change": True,
            "has_holdup": False,
            "delta_T_method": DeltaTMethod.counterCurrent,
            "tube_arrangement": TubeArrangement.inLine,
            "side_1_water_phase": "Liq",
            "has_radiation": True
        })

    # Check unit config arguments
    # There are 8 to 10 arguments since you can add a side 1 and 2 config by
    # side_1, side_2, or whatever the user named them
    assert len(m.fs.unit.config) >= 12 and len(m.fs.unit.config) <= 16

    assert not m.fs.unit.config.dynamic
    assert not m.fs.unit.config.has_holdup
    assert m.fs.unit.config.delta_T_method == \
        DeltaTMethod.counterCurrent
Example #3
0
def test_deprecated_delta_T_method(caplog):
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.prop_steam = iapws95.Iapws95ParameterBlock()
    m.fs.prop_fluegas = FlueGasParameterBlock()

    caplog.clear()
    m.fs.unit = BoilerHeatExchanger(
        default={
            "delta_temperature_callback": delta_temperature_lmtd_callback,
            "tube": {
                "property_package": m.fs.prop_steam
            },
            "shell": {
                "property_package": m.fs.prop_fluegas
            },
            "has_pressure_change": True,
            "has_holdup": True,
            "delta_T_method": HeatExchangerFlowPattern.countercurrent,
            "tube_arrangement": TubeArrangement.inLine,
            "side_1_water_phase": "Liq",
            "has_radiation": True,
        })
    n_warn = 0
    n_depreacted = 0
    for record in caplog.records:
        if record.levelno == idaeslog.WARNING:
            n_warn += 1
        if "deprecated" in record.msg:
            n_depreacted += 1
    assert n_warn == 1
    assert n_depreacted == 1
    assert m.fs.unit.config.flow_pattern == HeatExchangerFlowPattern.countercurrent
Example #4
0
def test_compressor_fan():
    m = pyo.ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})

    m.fs.properties = iapws95.Iapws95ParameterBlock()
    # Add property packages to flowsheet library
    m.fs.prop_fluegas = FlueGasParameterBlock()
    m.fs.unit = PressureChanger(
        default={
            "property_package": m.fs.prop_fluegas,
            "thermodynamic_assumption": ThermodynamicAssumption.isentropic,
            "compressor": True
        })

    # # FLUE GAS Inlet from Primary Superheater
    FGrate = 425.813998  # mol/s
    # # Use FG molar composition to set component flow rates (baseline report)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["H2O"].\
        fix(FGrate*8.69/100)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["CO2"].\
        fix(FGrate*14.49/100)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["N2"].\
        fix(FGrate*74.34/100)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["O2"].\
        fix(FGrate*2.47/100)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["NO"].\
        fix(FGrate*0.0006)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["SO2"]\
        .fix(FGrate*0.002)
    m.fs.unit.control_volume.properties_in[0].temperature.fix(200.335)
    m.fs.unit.control_volume.properties_in[0].pressure.fix(98658.6)

    m.fs.unit.deltaP.fix(101325 - 98658.6)
    m.fs.unit.efficiency_isentropic.fix(0.9)

    m.fs.unit.get_costing(mover_type='fan')

    m.fs.unit.initialize()
    # make sure costing initialized correctly
    assert (pytest.approx(pyo.value(m.fs.unit.costing.purchase_cost),
                          abs=1e-2) == 22106.9807)

    assert degrees_of_freedom(m) == 0
    # Check unit config arguments
    assert isinstance(m.fs.unit.costing.purchase_cost, pyo.Var)
    assert isinstance(m.fs.unit.costing.base_cost_per_unit, pyo.Var)
    results = solver.solve(m, tee=True)
    assert results.solver.termination_condition == \
        pyo.TerminationCondition.optimal
    assert results.solver.status == pyo.SolverStatus.ok
    assert (pytest.approx(pyo.value(m.fs.unit.costing.base_cost),
                          abs=1e-2) == 4543.6428)
    assert (pytest.approx(pyo.value(m.fs.unit.costing.purchase_cost),
                          abs=1e-2) == 22106.9807)
Example #5
0
def test_blower_build_and_solve():
    m = pyo.ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})

    m.fs.properties = iapws95.Iapws95ParameterBlock()
    # Add property packages to flowsheet library
    m.fs.prop_fluegas = FlueGasParameterBlock()
    m.fs.unit = PressureChanger(
        default={
            "property_package": m.fs.prop_fluegas,
            "thermodynamic_assumption": ThermodynamicAssumption.isentropic,
            "compressor": True
        })

    # # FLUE GAS Inlet from Primary Superheater
    FGrate = 8516.27996  # mol/s
    # # Use FG molar composition to set component flow rates (baseline report)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["H2O"].\
        fix(FGrate*8.69/100)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["CO2"].\
        fix(FGrate*14.49/100)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["N2"].\
        fix(FGrate*74.34/100)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["O2"].\
        fix(FGrate*2.47/100)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["NO"].\
        fix(FGrate*0.0006)
    m.fs.unit.control_volume.properties_in[0].flow_mol_comp["SO2"]\
        .fix(FGrate*0.002)
    m.fs.unit.control_volume.properties_in[0].temperature.fix(200.335)
    m.fs.unit.control_volume.properties_in[0].pressure.fix(99973.98)

    m.fs.unit.deltaP.fix(144790 - 99973.98)
    m.fs.unit.efficiency_isentropic.fix(0.9)
    m.fs.unit.initialize()
    m.fs.unit.get_costing(mover_type='fan')

    calculate_variable_from_constraint(m.fs.unit.costing.purchase_cost,
                                       m.fs.unit.costing.cp_cost_eq)
    assert degrees_of_freedom(m) == 0
    # Check unit config arguments
    assert isinstance(m.fs.unit.costing.purchase_cost, pyo.Var)
    assert isinstance(m.fs.unit.costing.base_cost, pyo.Var)
    results = solver.solve(m, tee=True)
    assert results.solver.termination_condition == \
        pyo.TerminationCondition.optimal
    assert results.solver.status == pyo.SolverStatus.ok
    assert (pytest.approx(pyo.value(m.fs.unit.costing.base_cost),
                          abs=1e-2) == 56026.447)
    assert (pytest.approx(pyo.value(m.fs.unit.costing.purchase_cost),
                          abs=1e-2) == 272595.280)
Example #6
0
def test_thermo():
    # Read in test data and add mixtures
    m = pyo.ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.params = FlueGasParameterBlock()
    m.fs.state = FlueGasStateBlock(default={"parameters": m.fs.params})
    m.fs.state.pressure.fix(
        1e5)  #ideal gas properties are pressure independent

    assert_units_consistent(m)

    data = read_data("pure-prop-nist-webbook.csv", m.fs.params)

    assert hasattr(m.fs.state, "cp_mol")
    assert hasattr(m.fs.state, "enth_mol")
    assert hasattr(m.fs.state, "entr_mol")

    m.fs.state.cp_mol = 30
    m.fs.state.enth_mol = 30000
    m.fs.state.entr_mol = 30

    for i in data:
        for T in data[i]:
            m.fs.state.temperature.fix(T)
            test_entropy = True
            for j, f in m.fs.state.flow_mol_comp.items():
                cf = data[i][T]["comp"].get(j, 0)
                if cf <= 0:
                    test_entropy = False
                f.fix(cf)

            if test_entropy:
                m.fs.state.entr_mol.unfix()
                m.fs.state.entropy_correlation.deactivate()
            else:
                m.fs.state.entr_mol.unfix()
                m.fs.state.entropy_correlation.deactivate()
            m.fs.state.initialize()
            solver.solve(m)
            assert data[i][T]["H"] == pytest.approx(pyo.value(
                m.fs.state.enth_mol),
                                                    rel=1e-2)
            assert data[i][T]["Cp"] == pytest.approx(pyo.value(
                m.fs.state.cp_mol),
                                                     rel=1e-2)
            if test_entropy:
                assert data[i][T]["S"] == pytest.approx(pyo.value(
                    m.fs.state.entr_mol),
                                                        rel=1e-2)
Example #7
0
def build_unit():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})

    m.fs.properties = PhysicalParameterTestBlock()
    m.fs.prop_fluegas = FlueGasParameterBlock()
    m.fs.unit = HeatExchangerWith3Streams(
        default={
            "side_1_property_package": m.fs.prop_fluegas,
            "side_2_property_package": m.fs.prop_fluegas,
            "side_3_property_package": m.fs.prop_fluegas,
            "has_heat_transfer": True,
            "has_pressure_change": True,
            "flow_type_side_2": "counter-current",
            "flow_type_side_3": "counter-current",
        })
    return m
def build_unit():
    # Create a Concrete Model as the top level object
    m = pyo.ConcreteModel()
    # Add a flowsheet object to the model
    m.fs = FlowsheetBlock(default={"dynamic": False})
    # Add property packages to flowsheet library
    m.fs.prop_fluegas = FlueGasParameterBlock()
    # boiler based on surrogate
    m.fs.unit = BoilerFireside(
        default={
            "dynamic": False,
            "property_package": m.fs.prop_fluegas,
            "calculate_PA_SA_flows": False,
            "number_of_zones": 12,
            "has_platen_superheater": True,
            "has_roof_superheater": True,
            "surrogate_dictionary": dat.data_dic
        })
    return m
def test_units():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})

    m.fs.properties = PhysicalParameterTestBlock()
    m.fs.prop_steam = iapws95.Iapws95ParameterBlock()
    m.fs.prop_fluegas = FlueGasParameterBlock()

    m.fs.unit = BoilerHeatExchanger(default={
        "side_1_property_package": m.fs.prop_steam,
        "side_2_property_package": m.fs.prop_fluegas,
        "has_pressure_change": True,
        "has_holdup": False,
        "delta_T_method": DeltaTMethod.counterCurrent,
        "tube_arrangement": TubeArrangement.inLine,
        "side_1_water_phase": "Liq",
        "has_radiation": True})

    assert_units_consistent(m)
Example #10
0
def build_unit():
    # Create a Concrete Model as the top level object
    m = pyo.ConcreteModel()
    # Add a flowsheet object to the model
    m.fs = FlowsheetBlock(default={"dynamic": False})
    # Add property packages to flowsheet library
    m.fs.prop_water = iapws95.Iapws95ParameterBlock()
    m.fs.prop_fluegas = FlueGasParameterBlock()
    # Add a 2D cross-flow heat exchanger with header model to the flowsheet
    m.fs.unit = HeatExchangerCrossFlow2D_Header(
        default={
            "tube_side": {
                "property_package": m.fs.prop_water,
                "has_pressure_change": True
            },
            "shell_side": {
                "property_package": m.fs.prop_fluegas,
                "has_pressure_change": True
            },
            "finite_elements": 5,
            "flow_type": "counter_current",
            "tube_arrangement": "in-line",
            "tube_side_water_phase": "Vap",
            "has_radiation": True,
            "radial_elements": 5,
            "tube_inner_diameter": 0.035,
            "tube_thickness": 0.0035,
            "has_header": True,
            "header_radial_elements": 5,
            "header_inner_diameter": 0.3,
            "header_wall_thickness": 0.03
        })

    # Primary Superheater (NETL baseline report)
    ITM = 0.0254  # inch to meter conversion
    # m.fs.unit.tube_di.fix((2.5-2*0.165)*ITM)
    # m.fs.unit.tube_thickness.fix(0.165*ITM)
    m.fs.unit.pitch_x.fix(3 * ITM)
    # gas path transverse width 54.78 ft / number of columns
    m.fs.unit.pitch_y.fix(54.78 / 108 * 12 * ITM)
    m.fs.unit.tube_length_seg.fix(53.13 * 12 * ITM)
    m.fs.unit.tube_nseg.fix(20 * 2)
    m.fs.unit.tube_ncol.fix(108)
    m.fs.unit.tube_inlet_nrow.fix(4)
    m.fs.unit.delta_elevation.fix(50)
    m.fs.unit.tube_r_fouling = 0.000176  # (0.001 h-ft^2-F/BTU)
    m.fs.unit.tube_r_fouling = 0.003131  # (0.03131 - 0.1779 h-ft^2-F/BTU)

    # material inputs
    m.fs.unit.therm_cond_wall = 43.0  # Carbon steel 1% C in W/m/K
    m.fs.unit.dens_wall = 7850  # kg/m3 or 0.284 lb/in3
    m.fs.unit.cp_wall = 510.8  # J/kg-K (0.05 to 0.25 % C)
    m.fs.unit.Young_modulus = 2.00e5  # 200 GPa (29,000 ksi)
    m.fs.unit.Possion_ratio = 0.29
    m.fs.unit.coefficient_therm_expansion = 1.3e-5

    m.fs.unit.emissivity_wall.fix(0.7)
    m.fs.unit.fcorrection_htc_tube.fix(1.0)
    m.fs.unit.fcorrection_htc_shell.fix(1.0)
    m.fs.unit.fcorrection_dp_tube.fix(1.0)
    m.fs.unit.fcorrection_dp_shell.fix(1.0)
    m.fs.unit.temperature_ambient.fix(350.0)
    m.fs.unit.head_insulation_thickness.fix(0.025)
    return m
def build_boiler(fs):

    # Add property packages to flowsheet library
    fs.prop_fluegas = FlueGasParameterBlock()

    # Create unit models
    # Boiler Economizer
    fs.ECON = BoilerHeatExchanger(
        default={
            "side_1_property_package": fs.prop_water,
            "side_2_property_package": fs.prop_fluegas,
            "has_pressure_change": True,
            "has_holdup": False,
            "delta_T_method": DeltaTMethod.counterCurrent,
            "tube_arrangement": TubeArrangement.inLine,
            "side_1_water_phase": "Liq",
            "has_radiation": False
        })
    # Primary Superheater
    fs.PrSH = BoilerHeatExchanger(
        default={
            "side_1_property_package": fs.prop_water,
            "side_2_property_package": fs.prop_fluegas,
            "has_pressure_change": True,
            "has_holdup": False,
            "delta_T_method": DeltaTMethod.counterCurrent,
            "tube_arrangement": TubeArrangement.inLine,
            "side_1_water_phase": "Vap",
            "has_radiation": True
        })

    # Finishing Superheater
    fs.FSH = BoilerHeatExchanger(
        default={
            "side_1_property_package": fs.prop_water,
            "side_2_property_package": fs.prop_fluegas,
            "has_pressure_change": True,
            "has_holdup": False,
            "delta_T_method": DeltaTMethod.counterCurrent,
            "tube_arrangement": TubeArrangement.inLine,
            "side_1_water_phase": "Vap",
            "has_radiation": True
        })

    # Reheater
    fs.RH = BoilerHeatExchanger(
        default={
            "side_1_property_package": fs.prop_water,
            "side_2_property_package": fs.prop_fluegas,
            "has_pressure_change": True,
            "has_holdup": False,
            "delta_T_method": DeltaTMethod.counterCurrent,
            "tube_arrangement": TubeArrangement.inLine,
            "side_1_water_phase": "Vap",
            "has_radiation": True
        })
    # Platen Superheater
    fs.PlSH = Heater(default={"property_package": fs.prop_water})

    # Boiler Water Wall
    fs.Water_wall = Heater(default={"property_package": fs.prop_water})

    # Boiler Splitter (splits FSH flue gas outlet to Reheater and PrSH)
    fs.Spl1 = Separator(
        default={
            "property_package": fs.prop_fluegas,
            "split_basis": SplittingType.totalFlow,
            "energy_split_basis": EnergySplittingType.equal_temperature
        })
    # Flue gas mixer (mixing FG from Reheater and Primary SH, inlet to ECON)
    fs.mix1 = Mixer(
        default={
            "property_package": fs.prop_fluegas,
            "inlet_list": ['Reheat_out', 'PrSH_out'],
            "dynamic": False
        })

    # Mixer for Attemperator #1 (between PrSH and PlSH)
    fs.ATMP1 = Mixer(
        default={
            "property_package": fs.prop_water,
            "inlet_list": ['Steam', 'SprayWater'],
            "dynamic": False
        })

    # Build connections (streams)

    # Steam Route (side 1 = tube side = steam/water side)
    # Boiler feed water to Economizer (to be imported in full plant model)
    #    fs.bfw2econ = Arc(source=fs.FWH8.outlet,
    #                           destination=fs.ECON.side_1_inlet)
    fs.econ2ww = Arc(source=fs.ECON.side_1_outlet,
                     destination=fs.Water_wall.inlet)
    fs.ww2prsh = Arc(source=fs.Water_wall.outlet,
                     destination=fs.PrSH.side_1_inlet)
    fs.prsh2plsh = Arc(source=fs.PrSH.side_1_outlet, destination=fs.PlSH.inlet)
    fs.plsh2fsh = Arc(source=fs.PlSH.outlet, destination=fs.FSH.side_1_inlet)
    fs.FSHtoATMP1 = Arc(source=fs.FSH.side_1_outlet,
                        destination=fs.ATMP1.Steam)
    #    fs.fsh2hpturbine=Arc(source=fs.ATMP1.outlet,
    #                           destination=fs.HPTinlet)
    # (to be imported in full plant model)

    # Flue gas route ---------------------------------------------------------
    # water wall connected with boiler block (to fix the heat duty)
    # platen SH connected with boiler block (to fix the heat duty)
    # Finishing superheater connected with a flowsheet level constraint
    fs.fg_fsh2_separator = Arc(source=fs.FSH.side_2_outlet,
                               destination=fs.Spl1.inlet)
    fs.fg_fsh2rh = Arc(source=fs.Spl1.outlet_1, destination=fs.RH.side_2_inlet)
    fs.fg_fsh2PrSH = Arc(source=fs.Spl1.outlet_2,
                         destination=fs.PrSH.side_2_inlet)
    fs.fg_rhtomix = Arc(source=fs.RH.side_2_outlet,
                        destination=fs.mix1.Reheat_out)
    fs.fg_prsh2mix = Arc(source=fs.PrSH.side_2_outlet,
                         destination=fs.mix1.PrSH_out)
    fs.fg_mix2econ = Arc(source=fs.mix1.outlet,
                         destination=fs.ECON.side_2_inlet)
def test_boiler_hx():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})

    m.fs.properties = PhysicalParameterTestBlock()
    m.fs.prop_steam = iapws95.Iapws95ParameterBlock()
    m.fs.prop_fluegas = FlueGasParameterBlock()

    m.fs.unit = BoilerHeatExchanger(
        default={
            "side_1_property_package": m.fs.prop_steam,
            "side_2_property_package": m.fs.prop_fluegas,
            "has_pressure_change": True,
            "has_holdup": False,
            "delta_T_method": DeltaTMethod.counterCurrent,
            "tube_arrangement": TubeArrangement.inLine,
            "side_1_water_phase": "Liq",
            "has_radiation": True
        })

    #   Set inputs
    h = iapws95.htpx(773.15, 2.5449e7)
    print(h)
    m.fs.unit.side_1_inlet.flow_mol[0].fix(24678.26)  # mol/s
    m.fs.unit.side_1_inlet.enth_mol[0].fix(h)  # J/mol
    m.fs.unit.side_1_inlet.pressure[0].fix(2.5449e7)  # Pascals

    # FLUE GAS Inlet from Primary Superheater
    FGrate = 28.3876e3 * 0.18  # mol/s equivalent of ~1930.08 klb/hr
    # Use FG molar composition to set component flow rates (baseline report)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "H2O"].fix(FGrate * 8.69 / 100)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "CO2"].fix(FGrate * 14.49 / 100)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "N2"].fix(FGrate * 74.34 / 100)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "O2"].fix(FGrate * 2.47 / 100)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "NO"].fix(FGrate * 0.0006)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "SO2"].fix(FGrate * 0.002)
    m.fs.unit.side_2_inlet.temperature[0].fix(1102.335)
    m.fs.unit.side_2_inlet.pressure[0].fix(100145)

    # Primary Superheater
    ITM = 0.0254  # inch to meter conversion
    m.fs.unit.tube_di.fix((2.5 - 2 * 0.165) * ITM)
    m.fs.unit.tube_thickness.fix(0.165 * ITM)
    m.fs.unit.pitch_x.fix(3 * ITM)
    # gas path transverse width 54.78 ft / number of columns
    m.fs.unit.pitch_y.fix(54.78 / 108 * 12 * ITM)
    m.fs.unit.tube_length.fix(53.13 * 12 * ITM)
    m.fs.unit.tube_nrow.fix(20 * 2)
    m.fs.unit.tube_ncol.fix(108)
    m.fs.unit.nrow_inlet.fix(4)
    m.fs.unit.delta_elevation.fix(50)
    m.fs.unit.tube_r_fouling = 0.000176  # (0.001 h-ft^2-F/BTU)
    m.fs.unit.tube_r_fouling = 0.003131  # (0.03131 - 0.1779 h-ft^2-F/BTU)
    if m.fs.unit.config.has_radiation is True:
        m.fs.unit.emissivity_wall.fix(0.7)  # wall emissivity
    # correction factor for overall heat transfer coefficient
    m.fs.unit.fcorrection_htc.fix(1.5)
    # correction factor for pressure drop calc tube side
    m.fs.unit.fcorrection_dp_tube.fix(1.0)
    # correction factor for pressure drop calc shell side
    m.fs.unit.fcorrection_dp_shell.fix(1.0)

    assert degrees_of_freedom(m) == 0

    m.fs.unit.initialize()
    # Create a solver
    solver = SolverFactory('ipopt')
    results = solver.solve(m)
    # Check for optimal solution
    assert results.solver.termination_condition == \
        TerminationCondition.optimal
    assert results.solver.status == SolverStatus.ok
    assert value(m.fs.unit.side_1.properties_out[0].temperature) == \
        pytest.approx(588.07, 1)
    assert value(m.fs.unit.side_2.properties_out[0].temperature) == \
        pytest.approx(573.07, 1)
Example #13
0
def th(
    delta_temperature_callback=delta_temperature_underwood_tune_callback,
    tout_1=809.55,
    tout_2=788.53,
):
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})

    m.fs.properties = PhysicalParameterTestBlock()
    m.fs.prop_steam = iapws95.Iapws95ParameterBlock()
    m.fs.prop_fluegas = FlueGasParameterBlock()

    m.fs.unit = BoilerHeatExchanger(
        default={
            "delta_temperature_callback": delta_temperature_callback,
            "tube": {
                "property_package": m.fs.prop_steam
            },
            "shell": {
                "property_package": m.fs.prop_fluegas
            },
            "has_pressure_change": True,
            "has_holdup": False,
            "flow_pattern": HeatExchangerFlowPattern.countercurrent,
            "tube_arrangement": TubeArrangement.inLine,
            "side_1_water_phase": "Liq",
            "has_radiation": True,
        })

    #   Set inputs
    h = value(iapws95.htpx(773.15 * pyunits.K, 2.5449e7 * pyunits.Pa))
    m.fs.unit.side_1_inlet.flow_mol[0].fix(24678.26)  # mol/s
    m.fs.unit.side_1_inlet.enth_mol[0].fix(h)  # J/mol
    m.fs.unit.side_1_inlet.pressure[0].fix(2.5449e7)  # Pascals

    # FLUE GAS Inlet from Primary Superheater
    FGrate = 28.3876e3 * 0.18  # mol/s equivalent of ~1930.08 klb/hr
    # Use FG molar composition to set component flow rates (baseline report)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "H2O"].fix(FGrate * 8.69 / 100)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "CO2"].fix(FGrate * 14.49 / 100)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "N2"].fix(FGrate * 74.34 / 100)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "O2"].fix(FGrate * 2.47 / 100)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "NO"].fix(FGrate * 0.0006)
    m.fs.unit.side_2_inlet.flow_mol_comp[0, "SO2"].fix(FGrate * 0.002)
    m.fs.unit.side_2_inlet.temperature[0].fix(1102.335)
    m.fs.unit.side_2_inlet.pressure[0].fix(100145)

    # Primary Superheater
    ITM = 0.0254  # inch to meter conversion
    m.fs.unit.tube_di.fix((2.5 - 2 * 0.165) * ITM)
    m.fs.unit.tube_thickness.fix(0.165 * ITM)
    m.fs.unit.pitch_x.fix(3 * ITM)
    # gas path transverse width 54.78 ft / number of columns
    m.fs.unit.pitch_y.fix(54.78 / 108 * 12 * ITM)
    m.fs.unit.tube_length.fix(53.13 * 12 * ITM)
    m.fs.unit.tube_nrow.fix(20 * 2)
    m.fs.unit.tube_ncol.fix(108)
    m.fs.unit.nrow_inlet.fix(4)
    m.fs.unit.delta_elevation.fix(50)
    m.fs.unit.tube_r_fouling = 0.000176  # (0.001 h-ft^2-F/BTU)
    m.fs.unit.tube_r_fouling = 0.003131  # (0.03131 - 0.1779 h-ft^2-F/BTU)
    if m.fs.unit.config.has_radiation is True:
        m.fs.unit.emissivity_wall.fix(0.7)  # wall emissivity
    # correction factor for overall heat transfer coefficient
    m.fs.unit.fcorrection_htc.fix(1.5)
    # correction factor for pressure drop calc tube side
    m.fs.unit.fcorrection_dp_tube.fix(1.0)
    # correction factor for pressure drop calc shell side
    m.fs.unit.fcorrection_dp_shell.fix(1.0)

    assert degrees_of_freedom(m) == 0
    iscale.calculate_scaling_factors(m)
    m.fs.unit.initialize()

    results = solver.solve(m)
    # Check for optimal solution
    assert check_optimal_termination(results)
    assert value(
        m.fs.unit.side_1.properties_out[0].temperature) == pytest.approx(
            tout_1, abs=0.5)
    assert value(
        m.fs.unit.side_2.properties_out[0].temperature) == pytest.approx(
            tout_2, abs=0.5)
Example #14
0
def get_model(dynamic=True, load_state=None, save_state=None, time_set=None, nstep=None):
    if load_state is not None and not os.path.exists(load_state):
        # Want to load state but file doesn't exist, so warn and reinitialize
        _log.warning(f"Warning cannot load saved state {load_state}")
        load_state = None

    m = pyo.ConcreteModel()
    m.dynamic = dynamic
    m.init_dyn = False
    if time_set is None:
        time_set = [0,20,200]
    if nstep is None:
        nstep = 5
    if m.dynamic:
        m.fs_main = FlowsheetBlock(default={"dynamic": True, "time_set": time_set})
    else:
        m.fs_main = FlowsheetBlock(default={"dynamic": False})
    # Add property packages to flowsheet library
    m.fs_main.prop_water = iapws95.Iapws95ParameterBlock()
    m.fs_main.prop_gas = FlueGasParameterBlock()
    m.fs_main.fs_blr = FlowsheetBlock()
    m.fs_main.fs_stc = FlowsheetBlock()
    m = blr.add_unit_models(m)
    m = stc.add_unit_models(m)
    if m.dynamic:
        # extra variables required by controllers
        # master level control output, desired feed water flow rate at bfp outlet
        m.fs_main.flow_level_ctrl_output = pyo.Var(
            m.fs_main.config.time,
            initialize=10000,
            doc="mole flow rate of feed water demand from drum level master controller"
        )
        # boiler master pv main steam pressure in MPa
        m.fs_main.main_steam_pressure = pyo.Var(
            m.fs_main.config.time,
            initialize=13,
            doc="main steam pressure in MPa for boiler master controller"
        )

        # PID controllers
        # master of cascading level controller
        m.fs_main.drum_master_ctrl = PIDController(default={"pv":m.fs_main.fs_blr.aDrum.level,
                                  "mv":m.fs_main.flow_level_ctrl_output,
                                  "type": 'PI'})
        # slave of cascading level controller
        m.fs_main.drum_slave_ctrl = PIDController(default={"pv":m.fs_main.fs_stc.bfp.outlet.flow_mol,
                                  "mv":m.fs_main.fs_stc.bfp_turb_valve.valve_opening,
                                  "type": 'PI',
                                  "bounded_output": False})
        # turbine master PID controller to control power output in MW by manipulating throttling valve
        m.fs_main.turbine_master_ctrl = PIDController(default={"pv":m.fs_main.fs_stc.power_output,
                                  "mv":m.fs_main.fs_stc.turb.throttle_valve[1].valve_opening,
                                  "type": 'PI'})
        # boiler master PID controller to control main steam pressure in MPa by manipulating coal feed rate
        m.fs_main.boiler_master_ctrl = PIDController(default={"pv":m.fs_main.main_steam_pressure,
                                  "mv":m.fs_main.fs_blr.aBoiler.flowrate_coal_raw,
                                  "type": 'PI'})

        m.discretizer = pyo.TransformationFactory('dae.finite_difference')
        m.discretizer.apply_to(m,
                           nfe=nstep,
                           wrt=m.fs_main.config.time,
                           scheme="BACKWARD")

        # desired sliding pressure in MPa as a function of power demand in MW
        @m.fs_main.Expression(m.fs_main.config.time, doc="Sliding pressure as a function of power output")
        def sliding_pressure(b, t):
            return 12.511952+(12.511952-9.396)/(249.234-96.8)*(b.turbine_master_ctrl.setpoint[t]-249.234)

        # main steam pressure in MPa
        @m.fs_main.Constraint(m.fs_main.config.time, doc="main steam pressure in MPa")
        def main_steam_pressure_eqn(b, t):
            return b.main_steam_pressure[t] == 1e-6*b.fs_blr.aPlaten.outlet.pressure[t]

        # Constraint for setpoint of the slave controller of the three-element drum level controller
        @m.fs_main.Constraint(m.fs_main.config.time, doc="Set point of drum level slave control")
        def drum_level_control_setpoint_eqn(b, t):
            return b.drum_slave_ctrl.setpoint[t] == b.flow_level_ctrl_output[t] + \
                   b.fs_blr.aPlaten.outlet.flow_mol[t] + \
                   b.fs_blr.blowdown_split.FW_Blowdown.flow_mol[t]  #revised to add steam flow only

        # Constraint for setpoint of boiler master
        @m.fs_main.Constraint(m.fs_main.config.time, doc="Set point of boiler master")
        def boiler_master_setpoint_eqn(b, t):
            return b.boiler_master_ctrl.setpoint[t] == 0.02*(b.turbine_master_ctrl.setpoint[t] - b.fs_stc.power_output[t]) + b.sliding_pressure[t]

        # Constraint for setpoint of boiler master
        @m.fs_main.Constraint(m.fs_main.config.time, doc="dry O2 in flue gas in dynamic mode")
        def dry_o2_in_flue_gas_dyn_eqn(b, t):
            return b.fs_blr.aBoiler.fluegas_o2_pct_dry[t] == 0.05*(b.fs_stc.spray_ctrl.setpoint[t] - b.fs_stc.temperature_main_steam[t]) - \
            0.0007652*b.fs_blr.aBoiler.flowrate_coal_raw[t]**3 + \
            0.06744*b.fs_blr.aBoiler.flowrate_coal_raw[t]**2 - 1.9815*b.fs_blr.aBoiler.flowrate_coal_raw[t] + 22.275

        # controller inputs
        # for master level controller
        m.fs_main.drum_master_ctrl.gain_p.fix(40000) #increased from 5000
        m.fs_main.drum_master_ctrl.gain_i.fix(100)
        m.fs_main.drum_master_ctrl.setpoint.fix(0.889)
        m.fs_main.drum_master_ctrl.mv_ref.fix(0)	#revised to 0
        # for slave level controller, note the setpoint is defined by the constraint
        m.fs_main.drum_slave_ctrl.gain_p.fix(2e-2)  # increased from 1e-2
        m.fs_main.drum_slave_ctrl.gain_i.fix(2e-4)  # increased from 1e-4
        m.fs_main.drum_slave_ctrl.mv_ref.fix(0.5)
        # for turbine master controller, note the setpoint is the power demand
        m.fs_main.turbine_master_ctrl.gain_p.fix(5e-4) #changed from 2e-3
        m.fs_main.turbine_master_ctrl.gain_i.fix(5e-4) #changed from 2e-3
        m.fs_main.turbine_master_ctrl.mv_ref.fix(0.6)
        # for boiler master controller, note the setpoint is specified by the constraint
        m.fs_main.boiler_master_ctrl.gain_p.fix(10)
        m.fs_main.boiler_master_ctrl.gain_i.fix(0.25)
        m.fs_main.boiler_master_ctrl.mv_ref.fix(29.0)

        t0 = m.fs_main.config.time.first()
        m.fs_main.drum_master_ctrl.integral_of_error[t0].fix(0)
        m.fs_main.drum_slave_ctrl.integral_of_error[t0].fix(0)
        m.fs_main.turbine_master_ctrl.integral_of_error[t0].fix(0)
        m.fs_main.boiler_master_ctrl.integral_of_error[t0].fix(0)

    blr.set_arcs_and_constraints(m)
    blr.set_inputs(m)
    stc.set_arcs_and_constraints(m)
    stc.set_inputs(m)
    # Now that the mole is discreteized set and calculate scaling factors

    set_scaling_factors(m)
    add_overall_performance_expressions(m)

    # Add performance measures
    if load_state is None:
        blr.initialize(m)
        stc.initialize(m)

    optarg={"tol":5e-7,"linear_solver":"ma27","max_iter":50}
    solver = pyo.SolverFactory("ipopt")
    solver.options = optarg

    _log.info("Bring models closer together...")
    m.fs_main.fs_blr.flow_mol_steam_rh_eqn.deactivate()
    # Hook the boiler to the steam cycle.
    m.fs_main.S001 = Arc(
        source=m.fs_main.fs_blr.aPlaten.outlet, destination=m.fs_main.fs_stc.turb.inlet_split.inlet
    )
    m.fs_main.S005 = Arc(
        source=m.fs_main.fs_stc.turb.hp_split[14].outlet_1, destination=m.fs_main.fs_blr.aRH1.tube_inlet
    )
    m.fs_main.S009 = Arc(
        source=m.fs_main.fs_blr.aRH2.tube_outlet, destination=m.fs_main.fs_stc.turb.ip_stages[1].inlet
    )
    m.fs_main.S042 = Arc(
        source=m.fs_main.fs_stc.fwh6.desuperheat.outlet_2, destination=m.fs_main.fs_blr.aECON.tube_inlet
    )
    m.fs_main.B006 = Arc(
        source=m.fs_main.fs_stc.spray_valve.outlet, destination=m.fs_main.fs_blr.Attemp.Water_inlet
    )
    pyo.TransformationFactory('network.expand_arcs').apply_to(m.fs_main)
    # unfix all connected streams
    m.fs_main.fs_stc.turb.inlet_split.inlet.unfix()
    m.fs_main.fs_stc.turb.hp_split[14].outlet_1.unfix()
    m.fs_main.fs_blr.aRH1.tube_inlet.unfix()
    m.fs_main.fs_stc.turb.ip_stages[1].inlet.unfix()
    m.fs_main.fs_blr.aECON.tube_inlet.unfix()
    m.fs_main.fs_blr.Attemp.Water_inlet.unfix()
    m.fs_main.fs_stc.spray_valve.outlet.unfix() #outlet pressure fixed on steam cycle sub-flowsheet
    # deactivate constraints on steam cycle flowsheet
    m.fs_main.fs_stc.fw_flow_constraint.deactivate()
    m.fs_main.fs_stc.turb.constraint_reheat_flow.deactivate()
    m.fs_main.fs_blr.aBoiler.flowrate_coal_raw.unfix() # steam circulation and coal flow are linked

    if m.dynamic==False:
        if load_state is None:
            m.fs_main.fs_stc.spray_valve.valve_opening.unfix()
            m.fs_main.fs_stc.temperature_main_steam.fix(810)
            _log.info("Solve connected models...")
            print("Degrees of freedom = {}".format(degrees_of_freedom(m)))
            assert degrees_of_freedom(m) == 0
            res = solver.solve(m, tee=True)
            _log.info("Solved: {}".format(idaeslog.condition(res)))
            # increase load to around 250 MW
            _log.info("Increase coal feed rate to 32.5...")
            m.fs_main.fs_stc.bfp.outlet.pressure.unfix()
            m.fs_main.fs_stc.turb.throttle_valve[1].valve_opening.fix(0.9)
            m.fs_main.fs_blr.aBoiler.flowrate_coal_raw.fix(32.5)
            res = solver.solve(m, tee=True)

        if not load_state is None:
            # the fwh heat transfer coefficient correlations are added in the
            # initialization, so if we are skipping the init, we have to add
            # them here.
            stc._add_heat_transfer_correlation(m.fs_main.fs_stc)
            ms.from_json(m, fname=load_state)

        if save_state is not None:
            ms.to_json(m, fname=save_state)

    else:
        m.fs_main.fs_blr.dry_o2_in_flue_gas_eqn.deactivate()
        t0 = m.fs_main.config.time.first()
        m.fs_main.fs_stc.fwh2.condense.level[t0].fix()
        m.fs_main.fs_stc.fwh3.condense.level[t0].fix()
        m.fs_main.fs_stc.fwh5.condense.level[t0].fix()
        m.fs_main.fs_stc.fwh6.condense.level[t0].fix()
        m.fs_main.fs_stc.hotwell_tank.level[t0].fix()
        m.fs_main.fs_stc.da_tank.level[t0].fix()
        m.fs_main.fs_stc.temperature_main_steam[t0].unfix()
        m.fs_main.fs_stc.spray_valve.valve_opening[t0].fix()
        m.fs_main.fs_blr.aDrum.level.unfix()
        m.fs_main.fs_blr.aDrum.level[t0].fix()
        m.fs_main.flow_level_ctrl_output.unfix()
        m.fs_main.flow_level_ctrl_output[t0].fix()
        m.fs_main.fs_stc.bfp.outlet.pressure.unfix()
        m.fs_main.fs_blr.aBoiler.flowrate_coal_raw.unfix()
        m.fs_main.fs_blr.aBoiler.flowrate_coal_raw[t0].fix()
        m.fs_main.fs_stc.turb.throttle_valve[1].valve_opening.unfix()
        m.fs_main.fs_stc.turb.throttle_valve[1].valve_opening[t0].fix()
        m.fs_main.turbine_master_ctrl.setpoint.fix()

    return m