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
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
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)
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)
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)
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)
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)
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)
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