def build_model(): m = ConcreteModel() # Properties comp_props = get_prop(components=["CO2", "H2O"], phases=["Vap", "Liq"]) # Parameters block m.params = GenericParameterBlock(default=comp_props) m.props = m.params.build_state_block( default={ "defined_state": True, "parameters": m.params, "has_phase_equilibrium": True }) m.props.flow_mol.fix(100) m.props.pressure.fix(101325) m.props.mole_frac_comp["CO2"].fix(0.94) m.props.mole_frac_comp["H2O"].fix(0.06) m.props.temperature_constraint = Constraint( expr=m.props.temperature == m.props.temperature_dew["Vap", "Liq"]) assert degrees_of_freedom(m) == 0 m.props.initialize(state_vars_fixed=True) results = get_solver(options={"bound_push": 1e-8}).solve(m) assert check_optimal_termination(results) return m
def m(self): m = ConcreteModel() m.fs = FlowsheetBlock(default={'dynamic': False}) m.fs.props = GenericParameterBlock(default=get_prop(components=[ 'H2', 'CO', "H2O", 'CO2', 'CH4', "C2H6", "C3H8", "C4H10", 'N2', 'O2', 'Ar' ])) m.fs.state = m.fs.props.build_state_block( [1], default={"defined_state": True}) return m
def test_construction_component_not_in_phase(): m = ConcreteModel() m.fs = FlowsheetBlock() m.fs.prop_params = GenericParameterBlock( default=get_prop(["H2O", "H2"], ["Liq", "Vap"])) m.fs.inject1 = Mixer( default={ "property_package": m.fs.prop_params, "inlet_list": ["in1", "in2"], "momentum_mixing_type": MomentumMixingType.none }) iscale.calculate_scaling_factors(m)
def test_gibbs(self, m): m.fs.props = GenericParameterBlock( default=get_prop(components=['H2', 'CO', 'H2O', 'CO2', 'O2', 'N2', 'Ar', 'CH4'])) m.fs.reactor = GibbsReactor(default={ "dynamic": False, "has_heat_transfer": True, "has_pressure_change": False, "property_package": m.fs.props}) m.fs.reactor.inlet.flow_mol.fix(8000) # mol/s m.fs.reactor.inlet.temperature.fix(600) # K m.fs.reactor.inlet.pressure.fix(1e6) # Pa m.fs.reactor.outlet.temperature.fix(600) # K m.fs.reactor.inlet.mole_frac_comp[0, "H2"].fix(0) m.fs.reactor.inlet.mole_frac_comp[0, "CO"].fix(0) m.fs.reactor.inlet.mole_frac_comp[0, "H2O"].fix(0.3) m.fs.reactor.inlet.mole_frac_comp[0, "CO2"].fix(0) m.fs.reactor.inlet.mole_frac_comp[0, "O2"].fix(0.2) m.fs.reactor.inlet.mole_frac_comp[0, "N2"].fix(0.05) m.fs.reactor.inlet.mole_frac_comp[0, "Ar"].fix(0.05) m.fs.reactor.inlet.mole_frac_comp[0, "CH4"].fix(0.4) constraint_scaling_transform(m.fs.reactor.control_volume.enthalpy_balances[0.0],1E-6) m.fs.reactor.gibbs_scaling = 1E-6 results = solver.solve(m, tee=True) assert results.solver.termination_condition == \ TerminationCondition.optimal assert results.solver.status == SolverStatus.ok assert 0.017 == pytest.approx(value( m.fs.reactor.outlet.mole_frac_comp[0, 'H2']), 1e-1) assert 0.0001 == pytest.approx(value( m.fs.reactor.outlet.mole_frac_comp[0, 'CO']), 1) assert 0.487 == pytest.approx(value( m.fs.reactor.outlet.mole_frac_comp[0, 'H2O']), 1e-1) assert 0.103 == pytest.approx(value( m.fs.reactor.outlet.mole_frac_comp[0, 'CO2']), 1e-1) assert 0.293 == pytest.approx(value( m.fs.reactor.outlet.mole_frac_comp[0, 'CH4']), 1e-1) assert -634e6 == pytest.approx(value( m.fs.reactor.heat_duty[0]), 1e-3)
def main( comps, rxns, phases, air_comp, ng_comp, m=None, initialize=True, flow_scale=0.896): """Generate and initialize the gas turbine flowsheet. This method returns a model and solver. The flow_scale argument can be used to scale the turnine up or down with the same relative performace. Args: comps: (set of str) a set of componets rxns: (dict) reactions with reaction name key and key component for conversion calculations phases: (list) phases potentially present air_comp: (dict) mole-fraction air composition ng_comp: (dict) mole-fraction natural gas composition m: (ConcreteModel) model to add flowsheet too. If None create a new one. initialize: (bool) If true initialize the flowsheet flow_scale: (float) flow scale multiplier to scale the performace curves up or down. Original nominal volumetric flow/new nominal volumetric flow (default=0.896). Returns: (ConcreteModel) model, (Solver) solver """ expand_arcs = pyo.TransformationFactory("network.expand_arcs") # # Model, flowsheet and properties # if m is None: m = pyo.ConcreteModel("Gas Turbine Model") m.fs = FlowsheetBlock(default={"dynamic": False}) m.fs.gas_prop_params = GenericParameterBlock(default=get_prop(comps, phases)) m.fs.gas_prop_params.set_default_scaling("mole_frac_comp", 10) m.fs.gas_prop_params.set_default_scaling("mole_frac_phase_comp", 10) _mf_scale = { "Ar":100, "O2":100, "H2S":1000, "SO2":1000, "H2":1000, "CO":1000, "C2H4":1000, "CO2":1000} for c, s in _mf_scale.items(): m.fs.gas_prop_params.set_default_scaling( "mole_frac_comp", s, index=c) m.fs.gas_prop_params.set_default_scaling( "mole_frac_phase_comp", s, index=("Vap", c)) m.fs.gas_prop_params.set_default_scaling( "enth_mol_phase", 1e-3, index="Vap") m.fs.gas_combustion = GenericReactionParameterBlock( default=get_rxn(m.fs.gas_prop_params, rxns)) # Variable for mole-fraction of O2 in the flue gas. To initialize the model # will use a fixed value here. m.fs.cmbout_o2_mol_frac = pyo.Var(m.fs.config.time, initialize=0.1157) m.fs.cmbout_o2_mol_frac.fix() # # Unit models # # inlet/outlet blocks m.fs.feed_air1 = um.Feed(default={"property_package": m.fs.gas_prop_params}) m.fs.feed_fuel1 = um.Feed(default={"property_package": m.fs.gas_prop_params}) m.fs.exhaust_1 = um.Product(default={"property_package": m.fs.gas_prop_params}) # Valve roughly approximating variable stator vanes to control air flow. m.fs.vsv = um.Valve(default={ "valve_function_callback":um.ValveFunctionType.linear, "property_package": m.fs.gas_prop_params}) # Comprssor for air m.fs.cmp1 = um.Compressor(default={ "property_package": m.fs.gas_prop_params, "support_isentropic_performance_curves":True}) # Blade cooling air splitter m.fs.splt1 = um.Separator(default={ "property_package": m.fs.gas_prop_params, "outlet_list":["air04", "air05", "air07", "air09"]}) # Three valves for blade cooling air. m.fs.valve01 = um.Valve(default={ "valve_function_callback":um.ValveFunctionType.linear, "property_package": m.fs.gas_prop_params}) m.fs.valve02 = um.Valve(default={ "valve_function_callback":um.ValveFunctionType.linear, "property_package": m.fs.gas_prop_params}) m.fs.valve03 = um.Valve(default={ "valve_function_callback":um.ValveFunctionType.linear, "property_package": m.fs.gas_prop_params}) # Mixer for fuel injection. Since this is a steady state model the pressure # drop on the fuel control valve is lumped into this mixer m.fs.inject1 = um.Mixer(default={ "property_package": m.fs.gas_prop_params, "inlet_list":["gas", "air"], "momentum_mixing_type":um.MomentumMixingType.none}) # Three blade cooling air mixers. The blade cooling isn't explicitly modeled # so these mainly just maintain the proper mass and enegy balance. m.fs.mx1 = um.Mixer(default={ "property_package": m.fs.gas_prop_params, "inlet_list":["gas", "air"], "momentum_mixing_type":um.MomentumMixingType.equality}) m.fs.mx2 = um.Mixer(default={ "property_package": m.fs.gas_prop_params, "inlet_list":["gas", "air"], "momentum_mixing_type":um.MomentumMixingType.equality}) m.fs.mx3 = um.Mixer(default={ "property_package": m.fs.gas_prop_params, "inlet_list":["gas", "air"], "momentum_mixing_type":um.MomentumMixingType.equality}) # Combustor, for now assuming complete reactions and no NOx m.fs.cmb1 = um.StoichiometricReactor(default={ "property_package": m.fs.gas_prop_params, "reaction_package": m.fs.gas_combustion, "has_pressure_change": True}) # Three gas turbine stages, with performance curves for all three added by # the performance_curves() fuctions. m.fs.gts1 = um.Turbine(default={ "property_package": m.fs.gas_prop_params, "support_isentropic_performance_curves":True}) m.fs.gts2 = um.Turbine(default={ "property_package": m.fs.gas_prop_params, "support_isentropic_performance_curves":True}) m.fs.gts3 = um.Turbine(default={ "property_package": m.fs.gas_prop_params, "support_isentropic_performance_curves":True}) performance_curves(m) # # Additional constraints/expressions # # to make this simpler, just deal with VSV valve-approximation pressure drop # and forget the opening m.fs.vsv.pressure_flow_equation.deactivate() # O2 fraction in combustor constraint (to set var) @m.fs.Constraint(m.fs.config.time) def o2_flow(b, t): return m.fs.cmb1.control_volume.properties_out[t].mole_frac_comp["O2"] \ == m.fs.cmbout_o2_mol_frac[t] # Fuel injector pressure is the air pressure. This lumps in the gas valve @m.fs.inject1.Constraint(m.fs.config.time) def mxpress_eqn(b, t): return b.mixed_state[t].pressure == b.air_state[t].pressure # The pressure drop in the cumbustor is just a fixed 5%. @m.fs.cmb1.Constraint(m.fs.config.time) def pressure_drop_eqn(b, t): return (0.95 == b.control_volume.properties_out[t].pressure / b.control_volume.properties_in[t].pressure) # Constraints for complete combustion, use key compoents and 100% conversion @m.fs.cmb1.Constraint(m.fs.config.time, rxns.keys()) def reaction_extent(b, t, r): k = rxns[r] prp = b.control_volume.properties_in[t] stc = -m.fs.gas_combustion.rate_reaction_stoichiometry[r, "Vap", k] extent = b.rate_reaction_extent[t, r] return extent == prp.flow_mol*prp.mole_frac_comp[k]/stc # Calculate the total gas turnine gross power output. @m.fs.Expression(m.fs.config.time) def gt_power_expr(b, t): return ( m.fs.cmp1.control_volume.work[t] + m.fs.gts1.control_volume.work[t] + m.fs.gts2.control_volume.work[t] + m.fs.gts3.control_volume.work[t]) # Add a varable and constraint for gross power. This allows fixing power # for simulations where a specific power output is desired. m.fs.gt_power = pyo.Var(m.fs.config.time) @m.fs.Constraint(m.fs.config.time) def gt_power_eqn(b, t): return b.gt_power[t] == b.gt_power_expr[t] # # Arcs # # Arc names are imoprtant here becuase they match the PFD and are used for # tagging stream quantities m.fs.fuel01 = Arc(source=m.fs.feed_fuel1.outlet, destination=m.fs.inject1.gas) m.fs.air01 = Arc(source=m.fs.feed_air1.outlet, destination=m.fs.vsv.inlet) m.fs.air02 = Arc(source=m.fs.vsv.outlet, destination=m.fs.cmp1.inlet) m.fs.air03 = Arc(source=m.fs.cmp1.outlet, destination=m.fs.splt1.inlet) m.fs.air04 = Arc(source=m.fs.splt1.air04, destination=m.fs.inject1.air) m.fs.air05 = Arc(source=m.fs.splt1.air05, destination=m.fs.valve01.inlet) m.fs.air06 = Arc(source=m.fs.valve01.outlet, destination=m.fs.mx1.air) m.fs.air07 = Arc(source=m.fs.splt1.air07, destination=m.fs.valve02.inlet) m.fs.air08 = Arc(source=m.fs.valve02.outlet, destination=m.fs.mx2.air) m.fs.air09 = Arc(source=m.fs.splt1.air09, destination=m.fs.valve03.inlet) m.fs.air10 = Arc(source=m.fs.valve03.outlet, destination=m.fs.mx3.air) m.fs.g01 = Arc(source=m.fs.inject1.outlet, destination=m.fs.cmb1.inlet) m.fs.g02 = Arc(source=m.fs.cmb1.outlet, destination=m.fs.gts1.inlet) m.fs.g03 = Arc(source=m.fs.gts1.outlet, destination=m.fs.mx1.gas) m.fs.g04 = Arc(source=m.fs.mx1.outlet, destination=m.fs.gts2.inlet) m.fs.g05 = Arc(source=m.fs.gts2.outlet, destination=m.fs.mx2.gas) m.fs.g06 = Arc(source=m.fs.mx2.outlet, destination=m.fs.gts3.inlet) m.fs.g07 = Arc(source=m.fs.gts3.outlet, destination=m.fs.mx3.gas) m.fs.g08 = Arc(source=m.fs.mx3.outlet, destination=m.fs.exhaust_1.inlet) # Create arc constraints expand_arcs.apply_to(m) # # Tag model # tags, tag_format = tag_model(m) # # Set some scaling # for i in ["air05", "air07", "air09"]: iscale.set_scaling_factor(m.fs.splt1.split_fraction[0.0, i], 100) iscale.set_scaling_factor(m.fs.valve01.control_volume.work, 1e-8) iscale.set_scaling_factor(m.fs.valve02.control_volume.work, 1e-8) iscale.set_scaling_factor(m.fs.valve03.control_volume.work, 1e-8) iscale.set_scaling_factor(m.fs.vsv.control_volume.work, 1e-8) iscale.set_scaling_factor(m.fs.cmp1.control_volume.work, 1e-8) iscale.set_scaling_factor(m.fs.gts1.control_volume.work, 1e-8) iscale.set_scaling_factor(m.fs.gts2.control_volume.work, 1e-8) iscale.set_scaling_factor(m.fs.gts3.control_volume.work, 1e-8) iscale.set_scaling_factor( m.fs.gts1.control_volume.properties_in[0].flow_mol, 1e-5) iscale.set_scaling_factor( m.fs.gts2.control_volume.properties_in[0].flow_mol, 1e-5) iscale.set_scaling_factor( m.fs.gts3.control_volume.properties_in[0].flow_mol, 1e-5) iscale.set_scaling_factor( m.fs.gts1.control_volume.properties_out[0].flow_mol, 1e-5) iscale.set_scaling_factor( m.fs.gts2.control_volume.properties_out[0].flow_mol, 1e-5) iscale.set_scaling_factor( m.fs.gts3.control_volume.properties_out[0].flow_mol, 1e-5) for i, v in m.fs.cmb1.control_volume.rate_reaction_extent.items(): if i[1] == "ch4_cmb": iscale.set_scaling_factor(v, 1e-2) else: iscale.set_scaling_factor(v, 1) for i, c in m.fs.cmb1.reaction_extent.items(): iscale.constraint_scaling_transform(c, 1e-2) for i, v in m.fs.cmb1.control_volume.rate_reaction_generation.items(): if i[2] in ["O2", "CO2", "CH4", "H2O"]: iscale.set_scaling_factor(v, 0.001) else: iscale.set_scaling_factor(v, 0.1) for v in m.fs.cmp1.deltaP.values(): iscale.set_scaling_factor(v, 1e-6) for v in m.fs.gts1.deltaP.values(): iscale.set_scaling_factor(v, 1e-6) for v in m.fs.gts2.deltaP.values(): iscale.set_scaling_factor(v, 1e-6) for v in m.fs.gts3.deltaP.values(): iscale.set_scaling_factor(v, 1e-6) for v in m.fs.valve01.deltaP.values(): iscale.set_scaling_factor(v, 1e-6) for v in m.fs.valve02.deltaP.values(): iscale.set_scaling_factor(v, 1e-6) for v in m.fs.valve03.deltaP.values(): iscale.set_scaling_factor(v, 1e-6) for c in m.fs.o2_flow.values(): iscale.constraint_scaling_transform(c, 10) for c in m.fs.gt_power_eqn.values(): iscale.constraint_scaling_transform(c, 1e-8) for c in m.fs.mx1.enthalpy_mixing_equations.values(): iscale.constraint_scaling_transform(c, 1e-8) for c in m.fs.mx2.enthalpy_mixing_equations.values(): iscale.constraint_scaling_transform(c, 1e-8) for c in m.fs.mx3.enthalpy_mixing_equations.values(): iscale.constraint_scaling_transform(c, 1e-8) for c in m.fs.inject1.enthalpy_mixing_equations.values(): iscale.constraint_scaling_transform(c, 1e-8) for c in m.fs.gts1.performance_curve.head_isen_eqn.values(): iscale.constraint_scaling_transform(c, 1e-5) for c in m.fs.gts1.performance_curve.eff_isen_eqn.values(): iscale.constraint_scaling_transform(c, 2) for c in m.fs.gts2.performance_curve.head_isen_eqn.values(): iscale.constraint_scaling_transform(c, 1e-5) for c in m.fs.gts2.performance_curve.eff_isen_eqn.values(): iscale.constraint_scaling_transform(c, 2) for c in m.fs.gts3.performance_curve.head_isen_eqn.values(): iscale.constraint_scaling_transform(c, 1e-5) for c in m.fs.gts3.performance_curve.eff_isen_eqn.values(): iscale.constraint_scaling_transform(c, 2) for c in m.fs.inject1.mxpress_eqn.values(): iscale.constraint_scaling_transform(c, 1e-5) # # Calculate scaling factors/scaling transform of constraints # iscale.calculate_scaling_factors(m) # # Set basic model inputs for initialization # # compressor m.fs.vsv.deltaP.fix(-100) m.fs.cmp1.efficiency_isentropic.fix(0.9) m.fs.cmp1.ratioP.fix(19.075) # blabe cooling air valves use expected flow to calculate valve flow # coefficients, so here set expected flow and after init the opening will # be the fixed var. m.fs.valve01.control_volume.properties_in[0].flow_mol.fix(2315) m.fs.valve02.control_volume.properties_in[0].flow_mol.fix(509) m.fs.valve03.control_volume.properties_in[0].flow_mol.fix(354) m.fs.valve01.valve_opening.unfix() m.fs.valve02.valve_opening.unfix() m.fs.valve03.valve_opening.unfix() # Feed streams # Air at compressor inlet m.fs.feed_air1.flow_mol[:] = 34875.9 m.fs.feed_air1.temperature.fix(288.15) m.fs.feed_air1.pressure.fix(101047) for i, v in air_comp.items(): m.fs.feed_air1.mole_frac_comp[:,i].fix(v) # Gas at fuel injection m.fs.feed_fuel1.flow_mol.fix(1348.8) m.fs.feed_fuel1.temperature.fix(310.928) m.fs.feed_fuel1.pressure.fix(3.10264e6) for i, v in ng_comp.items(): m.fs.feed_fuel1.mole_frac_comp[:,i].fix(v) # # Initialization # solver = pyo.SolverFactory('ipopt') if initialize: # feeds m.fs.feed_air1.initialize() m.fs.feed_fuel1.initialize() # compressor iutil.copy_port_values(m.fs.air01) m.fs.vsv.initialize() iutil.copy_port_values(m.fs.air02) m.fs.cmp1.initialize() # splitter iutil.copy_port_values(m.fs.air03) m.fs.splt1.split_fraction[0, "air05"].fix(0.0916985*0.73) m.fs.splt1.split_fraction[0, "air07"].fix(0.0916985*0.27*0.59) m.fs.splt1.split_fraction[0, "air09"].fix(0.0916985*0.27*0.41) m.fs.splt1.initialize() m.fs.splt1.split_fraction[0, "air05"].unfix() m.fs.splt1.split_fraction[0, "air07"].unfix() m.fs.splt1.split_fraction[0, "air09"].unfix() # inject iutil.copy_port_values(m.fs.air04) iutil.copy_port_values(m.fs.fuel01) m.fs.inject1.mixed_state[0].pressure = pyo.value(m.fs.inject1.air.pressure[0]) m.fs.inject1.initialize() # combustor iutil.copy_port_values(m.fs.g01) m.fs.cmb1.initialize() # gas turbine stage 1 iutil.copy_port_values(m.fs.g02) m.fs.gts1.ratioP[0] = 0.7 m.fs.gts1.initialize() # blade cooling air valve01, and calculate a flow coefficent iutil.copy_port_values(m.fs.air05) m.fs.valve01.Cv = 2 m.fs.valve01.Cv.unfix() m.fs.valve01.valve_opening.fix(0.85) m.fs.valve01.control_volume.properties_out[0].pressure.fix( pyo.value(m.fs.gts1.control_volume.properties_out[0].pressure)) m.fs.valve01.initialize() m.fs.valve01.control_volume.properties_out[0].pressure.unfix() m.fs.valve01.Cv.fix() m.fs.valve01.valve_opening.unfix() # mixer 1 iutil.copy_port_values(m.fs.air06) iutil.copy_port_values(m.fs.g03) m.fs.mx1.mixed_state[0].pressure = pyo.value(m.fs.mx1.gas.pressure[0]) m.fs.mx1.initialize() # gas turbine stage 2 iutil.copy_port_values(m.fs.g04) m.fs.gts2.ratioP[0] = 0.7 m.fs.gts2.initialize() # blade cooling air valve02, and calculate a flow coefficent iutil.copy_port_values(m.fs.air07) m.fs.valve02.Cv = 2 m.fs.valve02.Cv.unfix() m.fs.valve02.valve_opening.fix(0.85) m.fs.valve02.control_volume.properties_out[0].pressure.fix( pyo.value(m.fs.gts2.control_volume.properties_out[0].pressure)) m.fs.valve02.initialize() m.fs.valve02.control_volume.properties_out[0].pressure.unfix() m.fs.valve02.Cv.fix() m.fs.valve02.valve_opening.unfix() # mixer 2 iutil.copy_port_values(m.fs.air08) iutil.copy_port_values(m.fs.g05) m.fs.mx2.mixed_state[0].pressure = pyo.value(m.fs.mx2.gas.pressure[0]) m.fs.mx2.initialize() # gas turbine stage 3 iutil.copy_port_values(m.fs.g06) m.fs.gts3.ratioP[0] = 0.7 m.fs.gts3.initialize() # blade cooling air valve03, and calculate a flow coefficent iutil.copy_port_values(m.fs.air09) m.fs.valve03.Cv = 2 m.fs.valve03.Cv.unfix() m.fs.valve03.valve_opening.fix(0.85) m.fs.valve03.control_volume.properties_out[0].pressure.fix( pyo.value(m.fs.gts3.control_volume.properties_out[0].pressure)) m.fs.valve03.initialize() m.fs.valve03.control_volume.properties_out[0].pressure.unfix() m.fs.valve03.Cv.fix() m.fs.valve03.valve_opening.unfix() # mixer 3 iutil.copy_port_values(m.fs.air10) iutil.copy_port_values(m.fs.g07) m.fs.mx3.mixed_state[0].pressure = pyo.value(m.fs.mx3.gas.pressure[0]) m.fs.mx3.initialize() # product blocks # product blocks iutil.copy_port_values(m.fs.g08) m.fs.exhaust_1.initialize() # Solve solver.solve(m, tee=True) return m, solver