def main():
    """
    Make the flowsheet object, fix some variables, and solve the problem
    """
    # Create a Concrete Model as the top level object
    m = ConcreteModel()

    # Add a flowsheet object to the model
    # time_set has points at 0 and 20 as the start and end of the domain,
    # and a point at t=1 to allow for a step-change at this time
    m.fs = FlowsheetBlock(default={"dynamic": True, "time_set": [0, 1, 20]})

    # Add property packages to flowsheet library
    m.fs.thermo_params = thermo_props.SaponificationParameterBlock()
    m.fs.reaction_params = reaction_props.SaponificationReactionParameterBlock(
        default={"property_package": m.fs.thermo_params})

    # Create unit models
    m.fs.mix = Mixer(default={
        "dynamic": False,
        "property_package": m.fs.thermo_params
    })
    m.fs.Tank1 = CSTR(
        default={
            "property_package": m.fs.thermo_params,
            "reaction_package": m.fs.reaction_params,
            "has_holdup": True,
            "has_equilibrium_reactions": False,
            "has_heat_of_reaction": True,
            "has_heat_transfer": True,
            "has_pressure_change": False
        })
    m.fs.Tank2 = CSTR(
        default={
            "property_package": m.fs.thermo_params,
            "reaction_package": m.fs.reaction_params,
            "has_holdup": True,
            "has_equilibrium_reactions": False,
            "has_heat_of_reaction": True,
            "has_heat_transfer": True,
            "has_pressure_change": False
        })

    # Add pressure-flow constraints to Tank 1
    m.fs.Tank1.height = Var(m.fs.time,
                            initialize=1.0,
                            doc="Depth of fluid in tank [m]")
    m.fs.Tank1.area = Var(initialize=1.0,
                          doc="Cross-sectional area of tank [m^2]")
    m.fs.Tank1.flow_coeff = Var(m.fs.time,
                                initialize=5e-5,
                                doc="Tank outlet flow coefficient")

    def geometry(b, t):
        return b.volume[t] == b.area * b.height[t]

    m.fs.Tank1.geometry = Constraint(m.fs.time, rule=geometry)

    def outlet_flowrate(b, t):
        return b.control_volume.properties_out[t].flow_vol == \
                    b.flow_coeff[t]*b.height[t]**0.5

    m.fs.Tank1.outlet_flowrate = Constraint(m.fs.time, rule=outlet_flowrate)

    # Add pressure-flow constraints to tank 2
    m.fs.Tank2.height = Var(m.fs.time,
                            initialize=1.0,
                            doc="Depth of fluid in tank [m]")
    m.fs.Tank2.area = Var(initialize=1.0,
                          doc="Cross-sectional area of tank [m^2]")
    m.fs.Tank2.flow_coeff = Var(m.fs.time,
                                initialize=5e-5,
                                doc="Tank outlet flow coefficient")

    m.fs.Tank2.geometry = Constraint(m.fs.time, rule=geometry)
    m.fs.Tank2.outlet_flowrate = Constraint(m.fs.time, rule=outlet_flowrate)

    # Make Streams to connect units
    m.fs.stream1 = Arc(source=m.fs.mix.outlet, destination=m.fs.Tank1.inlet)

    m.fs.stream2 = Arc(source=m.fs.Tank1.outlet, destination=m.fs.Tank2.inlet)

    # Discretize time domain
    m.discretizer = TransformationFactory('dae.finite_difference')
    m.discretizer.apply_to(m, nfe=50, wrt=m.fs.time, scheme="BACKWARD")

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

    # Set inlet and operating conditions, and some initial conditions.
    m.fs.mix.inlet_1.flow_vol.fix(0.5)
    m.fs.mix.inlet_1.conc_mol_comp[:, "H2O"].fix(55388.0)
    m.fs.mix.inlet_1.conc_mol_comp[:, "NaOH"].fix(100.0)
    m.fs.mix.inlet_1.conc_mol_comp[:, "EthylAcetate"].fix(0.0)
    m.fs.mix.inlet_1.conc_mol_comp[:, "SodiumAcetate"].fix(0.0)
    m.fs.mix.inlet_1.conc_mol_comp[:, "Ethanol"].fix(0.0)
    m.fs.mix.inlet_1.temperature.fix(303.15)
    m.fs.mix.inlet_1.pressure.fix(101325.0)

    m.fs.mix.inlet_2.flow_vol.fix(0.5)
    m.fs.mix.inlet_2.conc_mol_comp[:, "H2O"].fix(55388.0)
    m.fs.mix.inlet_2.conc_mol_comp[:, "NaOH"].fix(0.0)
    m.fs.mix.inlet_2.conc_mol_comp[:, "EthylAcetate"].fix(100.0)
    m.fs.mix.inlet_2.conc_mol_comp[:, "SodiumAcetate"].fix(0.0)
    m.fs.mix.inlet_2.conc_mol_comp[:, "Ethanol"].fix(0.0)
    m.fs.mix.inlet_2.temperature.fix(303.15)
    m.fs.mix.inlet_2.pressure.fix(101325.0)

    m.fs.Tank1.area.fix(1.0)
    m.fs.Tank1.flow_coeff.fix(0.5)
    m.fs.Tank1.heat_duty.fix(0.0)

    m.fs.Tank2.area.fix(1.0)
    m.fs.Tank2.flow_coeff.fix(0.5)
    m.fs.Tank2.heat_duty.fix(0.0)

    # Set initial conditions - accumulation = 0 at time = 0
    m.fs.fix_initial_conditions(state="steady-state")

    # Initialize Units
    m.fs.mix.initialize()

    m.fs.Tank1.initialize(
        state_args={
            "flow_vol": 1.0,
            "conc_mol_comp": {
                "H2O": 55388.0,
                "NaOH": 100.0,
                "EthylAcetate": 100.0,
                "SodiumAcetate": 0.0,
                "Ethanol": 0.0
            },
            "temperature": 303.15,
            "pressure": 101325.0
        })

    m.fs.Tank2.initialize(
        state_args={
            "flow_vol": 1.0,
            "conc_mol_comp": {
                "H2O": 55388.0,
                "NaOH": 100.0,
                "EthylAcetate": 100.0,
                "SodiumAcetate": 0.0,
                "Ethanol": 0.0
            },
            "temperature": 303.15,
            "pressure": 101325.0
        })

    # Create a solver
    solver = SolverFactory('ipopt')
    results = solver.solve(m.fs)

    # Make a step disturbance in feed and solve again
    for t in m.fs.time:
        if t >= 1.0:
            m.fs.mix.inlet_2.conc_mol_comp[t, "EthylAcetate"].fix(90.0)
    results = solver.solve(m.fs)

    # Print results
    print(results)

    # For testing purposes
    return (m, results)
def test_heat_exchanger():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.properties = props.SeawaterParameterBlock()
    m.fs.unit = HeatExchanger(
        default={
            "hot_side_name": "hot",
            "cold_side_name": "cold",
            "hot": {
                "property_package": m.fs.properties
            },
            "cold": {
                "property_package": m.fs.properties
            },
            "delta_temperature_callback": delta_temperature_chen_callback,
            "flow_pattern": HeatExchangerFlowPattern.countercurrent,
        })

    # scaling
    m.fs.properties.set_default_scaling("flow_mass_phase_comp",
                                        1,
                                        index=("Liq", "H2O"))
    m.fs.properties.set_default_scaling("flow_mass_phase_comp",
                                        1e2,
                                        index=("Liq", "TDS"))
    iscale.set_scaling_factor(m.fs.unit.hot.heat, 1e-3)
    iscale.set_scaling_factor(m.fs.unit.cold.heat, 1e-3)
    iscale.set_scaling_factor(m.fs.unit.overall_heat_transfer_coefficient,
                              1e-3)
    iscale.set_scaling_factor(m.fs.unit.area, 1)
    iscale.calculate_scaling_factors(m)

    # ---specifications---
    # state variables
    m.fs.unit.hot_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(1)
    m.fs.unit.hot_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix(0.01)
    m.fs.unit.hot_inlet.temperature[0].fix(350)
    m.fs.unit.hot_inlet.pressure[0].fix(2e5)

    m.fs.unit.cold_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(0.5)
    m.fs.unit.cold_inlet.flow_mass_phase_comp[0, "Liq", "TDS"].fix(0.01)
    m.fs.unit.cold_inlet.temperature[0].fix(298)
    m.fs.unit.cold_inlet.pressure[0].fix(2e5)

    m.fs.unit.area.fix(5)
    m.fs.unit.overall_heat_transfer_coefficient.fix(1000)

    # solving
    assert_units_consistent(m)
    degrees_of_freedom(m)

    m.fs.unit.initialize()

    solver = get_solver()
    results = solver.solve(m, tee=False)
    assert_optimal_termination(results)

    assert pytest.approx(89050.0, rel=1e-4) == value(m.fs.unit.heat_duty[0])
    assert pytest.approx(1.0, rel=1e-4) == value(
        m.fs.unit.hot_outlet.flow_mass_phase_comp[0, "Liq", "H2O"])
    assert pytest.approx(0.01, rel=1e-4) == value(
        m.fs.unit.hot_outlet.flow_mass_phase_comp[0, "Liq", "TDS"])
    assert pytest.approx(328.69, rel=1e-4) == value(
        m.fs.unit.hot_outlet.temperature[0])
    assert pytest.approx(2.0e5,
                         rel=1e-4) == value(m.fs.unit.hot_outlet.pressure[0])
    assert pytest.approx(0.5, rel=1e-4) == value(
        m.fs.unit.cold_outlet.flow_mass_phase_comp[0, "Liq", "H2O"])
    assert pytest.approx(0.01, rel=1e-4) == value(
        m.fs.unit.cold_outlet.flow_mass_phase_comp[0, "Liq", "TDS"])
    assert pytest.approx(340.78, rel=1e-4) == value(
        m.fs.unit.cold_outlet.temperature[0])
    assert pytest.approx(2.0e5,
                         rel=1e-4) == value(m.fs.unit.cold_outlet.pressure[0])

    m.fs.unit.report()
Esempio n. 3
0
def build_valve_vapor():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.properties = iapws95.Iapws95ParameterBlock()
    m.fs.valve = SteamValve(default={"property_package": m.fs.properties})
    return m
Esempio n. 4
0
def build():
    # flowsheet set up
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.properties = props.NaClParameterBlock()
    m.fs.costing = WaterTAPCosting()

    # unit models
    m.fs.feed = Feed(default={"property_package": m.fs.properties})
    m.fs.S1 = Separator(
        default={"property_package": m.fs.properties, "outlet_list": ["P1", "PXR"]}
    )
    m.fs.P1 = Pump(default={"property_package": m.fs.properties})
    m.fs.PXR = PressureExchanger(default={"property_package": m.fs.properties})
    m.fs.P2 = Pump(default={"property_package": m.fs.properties})
    m.fs.M1 = Mixer(
        default={
            "property_package": m.fs.properties,
            "momentum_mixing_type": MomentumMixingType.equality,  # booster pump will match pressure
            "inlet_list": ["P1", "P2"],
        }
    )
    m.fs.RO = ReverseOsmosis0D(
        default={
            "property_package": m.fs.properties,
            "has_pressure_change": True,
            "pressure_change_type": PressureChangeType.calculated,
            "mass_transfer_coefficient": MassTransferCoefficient.calculated,
            "concentration_polarization_type": ConcentrationPolarizationType.calculated,
        }
    )
    m.fs.product = Product(default={"property_package": m.fs.properties})
    m.fs.disposal = Product(default={"property_package": m.fs.properties})

    # costing
    m.fs.P1.costing = UnitModelCostingBlock(
        default={"flowsheet_costing_block": m.fs.costing}
    )
    m.fs.P2.costing = UnitModelCostingBlock(
        default={"flowsheet_costing_block": m.fs.costing}
    )

    m.fs.RO.costing = UnitModelCostingBlock(
        default={"flowsheet_costing_block": m.fs.costing}
    )
    m.fs.PXR.costing = UnitModelCostingBlock(
        default={"flowsheet_costing_block": m.fs.costing}
    )
    m.fs.costing.cost_process()
    m.fs.costing.add_annual_water_production(m.fs.product.properties[0].flow_vol)
    m.fs.costing.add_LCOW(m.fs.product.properties[0].flow_vol)
    m.fs.costing.add_specific_energy_consumption(m.fs.product.properties[0].flow_vol)

    # connections
    m.fs.s01 = Arc(source=m.fs.feed.outlet, destination=m.fs.S1.inlet)
    m.fs.s02 = Arc(source=m.fs.S1.P1, destination=m.fs.P1.inlet)
    m.fs.s03 = Arc(source=m.fs.P1.outlet, destination=m.fs.M1.P1)
    m.fs.s04 = Arc(source=m.fs.M1.outlet, destination=m.fs.RO.inlet)
    m.fs.s05 = Arc(source=m.fs.RO.permeate, destination=m.fs.product.inlet)
    m.fs.s06 = Arc(source=m.fs.RO.retentate, destination=m.fs.PXR.high_pressure_inlet)
    m.fs.s07 = Arc(
        source=m.fs.PXR.high_pressure_outlet, destination=m.fs.disposal.inlet
    )
    m.fs.s08 = Arc(source=m.fs.S1.PXR, destination=m.fs.PXR.low_pressure_inlet)
    m.fs.s09 = Arc(source=m.fs.PXR.low_pressure_outlet, destination=m.fs.P2.inlet)
    m.fs.s10 = Arc(source=m.fs.P2.outlet, destination=m.fs.M1.P2)
    TransformationFactory("network.expand_arcs").apply_to(m)

    # scaling
    # set default property values
    m.fs.properties.set_default_scaling("flow_mass_phase_comp", 1, index=("Liq", "H2O"))
    m.fs.properties.set_default_scaling(
        "flow_mass_phase_comp", 1e2, index=("Liq", "NaCl")
    )
    # set unit model values
    iscale.set_scaling_factor(m.fs.P1.control_volume.work, 1e-3)
    iscale.set_scaling_factor(m.fs.P2.control_volume.work, 1e-3)
    iscale.set_scaling_factor(m.fs.PXR.low_pressure_side.work, 1e-3)
    iscale.set_scaling_factor(m.fs.PXR.high_pressure_side.work, 1e-3)
    # touch properties used in specifying and initializing the model
    m.fs.feed.properties[0].flow_vol_phase["Liq"]
    m.fs.feed.properties[0].mass_frac_phase_comp["Liq", "NaCl"]
    m.fs.S1.mixed_state[0].mass_frac_phase_comp
    m.fs.S1.PXR_state[0].flow_vol_phase["Liq"]
    # unused scaling factors needed by IDAES base costing module
    # calculate and propagate scaling factors
    iscale.calculate_scaling_factors(m)

    return m
Esempio n. 5
0
def main():
    # Create a Concrete Model as the top level object
    m = ConcreteModel()

    # Add a flowsheet object to the model
    m.fs = FlowsheetBlock(default={"dynamic": False})

    # Add property packages to flowsheet library
    m.fs.thermo_params = thermo_props.HDAParameterBlock()
    m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(
        default={"property_package": m.fs.thermo_params})

    # Create unit models
    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"]
        })

    # This is needed to avoid pressure degeneracy in recylce loop
    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.H101.control_volume.scaling_factor_energy = 1e-3
    m.fs.R101.control_volume.scaling_factor_energy = 1e-3
    m.fs.F101.control_volume.scaling_factor_energy = 1e-3
    m.fs.C101.control_volume.scaling_factor_energy = 1e-3
    m.fs.F102.control_volume.scaling_factor_energy = 1e-3

    # Connect units
    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)

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

    # Set operating conditions
    m.fs.M101.toluene_feed.flow_mol_phase_comp[0, "Vap", "benzene"].fix(1e-5)
    m.fs.M101.toluene_feed.flow_mol_phase_comp[0, "Vap", "toluene"].fix(1e-5)
    m.fs.M101.toluene_feed.flow_mol_phase_comp[0, "Vap", "hydrogen"].fix(1e-5)
    m.fs.M101.toluene_feed.flow_mol_phase_comp[0, "Vap", "methane"].fix(1e-5)
    m.fs.M101.toluene_feed.flow_mol_phase_comp[0, "Liq", "benzene"].fix(1e-5)
    m.fs.M101.toluene_feed.flow_mol_phase_comp[0, "Liq", "toluene"].fix(0.30)
    m.fs.M101.toluene_feed.flow_mol_phase_comp[0, "Liq", "hydrogen"].fix(1e-5)
    m.fs.M101.toluene_feed.flow_mol_phase_comp[0, "Liq", "methane"].fix(1e-5)
    m.fs.M101.toluene_feed.temperature.fix(303.2)
    m.fs.M101.toluene_feed.pressure.fix(350000)

    m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, "Vap", "benzene"].fix(1e-5)
    m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, "Vap", "toluene"].fix(1e-5)
    m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, "Vap", "hydrogen"].fix(0.30)
    m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, "Vap", "methane"].fix(0.02)
    m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, "Liq", "benzene"].fix(1e-5)
    m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, "Liq", "toluene"].fix(1e-5)
    m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, "Liq", "hydrogen"].fix(1e-5)
    m.fs.M101.hydrogen_feed.flow_mol_phase_comp[0, "Liq", "methane"].fix(1e-5)
    m.fs.M101.hydrogen_feed.temperature.fix(303.2)
    m.fs.M101.hydrogen_feed.pressure.fix(350000)

    m.fs.H101.outlet.temperature.fix(600)

    m.fs.R101.conversion = Var(initialize=0.75, bounds=(0, 1))

    m.fs.R101.conv_constraint = Constraint(
        expr=m.fs.R101.conversion *
        m.fs.R101.inlet.flow_mol_phase_comp[0, "Vap", "toluene"] == (
            m.fs.R101.inlet.flow_mol_phase_comp[0, "Vap", "toluene"] -
            m.fs.R101.outlet.flow_mol_phase_comp[0, "Vap", "toluene"]))

    m.fs.R101.conversion.fix(0.75)
    m.fs.R101.heat_duty.fix(0)

    m.fs.F101.vap_outlet.temperature.fix(325.0)
    m.fs.F101.deltaP.fix(0)

    m.fs.S101.split_fraction[0, "purge"].fix(0.2)

    m.fs.C101.outlet.pressure.fix(350000)

    m.fs.F102.vap_outlet.temperature.fix(375)
    m.fs.F102.deltaP.fix(-200000)

    # Define expressions
    # Product purity
    m.fs.purity = Expression(
        expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, "Vap", "benzene"] /
        (m.fs.F102.vap_outlet.flow_mol_phase_comp[0, "Vap", "benzene"] +
         m.fs.F102.vap_outlet.flow_mol_phase_comp[0, "Vap", "toluene"]))

    # Operating cost ($/yr)
    m.fs.cooling_cost = Expression(expr=0.212e-7 * -m.fs.F101.heat_duty[0] +
                                   0.212e-7 * -m.fs.R101.heat_duty[0])
    m.fs.heating_cost = Expression(expr=2.2e-7 * m.fs.H101.heat_duty[0] +
                                   1.9e-7 * m.fs.F102.heat_duty[0])
    m.fs.operating_cost = Expression(
        expr=(3600 * 24 * 365 * (m.fs.heating_cost + m.fs.cooling_cost)))
    print(degrees_of_freedom(m))

    # Initialize Units
    # Define method for initialising each block
    def function(unit):
        unit.initialize(outlvl=1)

    # Create instance of sequential decomposition tool
    seq = SequentialDecomposition()
    seq.options.select_tear_method = "heuristic"
    seq.options.tear_method = "Wegstein"
    seq.options.iterLim = 5

    # Determine tear stream and calculation order
    G = seq.create_graph(m)
    heu_result = seq.tear_set_arcs(G, method="heuristic")
    order = seq.calculation_order(G)

    # Display tear stream and calculation order
    for o in heu_result:
        print(o.name)
    for o in order:
        for oo in o:
            print(oo.name)

    # Set guesses for tear stream
    tear_guesses = {
        "flow_mol_phase_comp": {
            (0, "Vap", "benzene"): 1e-5,
            (0, "Vap", "toluene"): 1e-5,
            (0, "Vap", "hydrogen"): 0.30,
            (0, "Vap", "methane"): 0.02,
            (0, "Liq", "benzene"): 1e-5,
            (0, "Liq", "toluene"): 0.30,
            (0, "Liq", "hydrogen"): 1e-5,
            (0, "Liq", "methane"): 1e-5
        },
        "temperature": {
            0: 303
        },
        "pressure": {
            0: 350000
        }
    }
    seq.set_guesses_for(m.fs.H101.inlet, tear_guesses)

    # Run sequential initialisation
    seq.run(m, function)

    # # Create a solver
    solver = SolverFactory('ipopt')
    solver.options = {'tol': 1e-6}
    solver.options = {'tol': 1e-6, 'max_iter': 5000}
    results = solver.solve(m, tee=True)

    # Print results
    print("M101 Outlet")
    m.fs.M101.outlet.display()

    print("H101 Outlet")
    m.fs.H101.outlet.display()

    print("R101 Outlet")
    m.fs.R101.outlet.display()

    print("F101")
    m.fs.F101.liq_outlet.display()
    m.fs.F101.vap_outlet.display()

    print("F102")
    m.fs.F102.liq_outlet.display()
    m.fs.F102.vap_outlet.display()

    print("Purge")
    m.fs.S101.purge.display()

    print("Purity:", value(m.fs.purity))

    # Optimize process
    m.fs.objective = Objective(sense=minimize, expr=m.fs.operating_cost)

    # Decision variables
    m.fs.H101.outlet.temperature.unfix()
    m.fs.R101.heat_duty.unfix()
    m.fs.F101.vap_outlet.temperature.unfix()
    m.fs.F102.vap_outlet.temperature.unfix()
    m.fs.F102.deltaP.unfix()

    # Variable bounds
    m.fs.H101.outlet.temperature[0].setlb(500)
    m.fs.H101.outlet.temperature[0].setub(600)
    m.fs.R101.outlet.temperature[0].setlb(600)
    m.fs.R101.outlet.temperature[0].setub(800)
    m.fs.F101.vap_outlet.temperature[0].setlb(298.0)
    m.fs.F101.vap_outlet.temperature[0].setub(450.0)
    m.fs.F102.vap_outlet.temperature[0].setlb(298.0)
    m.fs.F102.vap_outlet.temperature[0].setub(450.0)
    m.fs.F102.vap_outlet.pressure[0].setlb(105000)
    m.fs.F102.vap_outlet.pressure[0].setub(110000)

    # Additional Constraints
    m.fs.overhead_loss = Constraint(
        expr=m.fs.F101.vap_outlet.flow_mol_phase_comp[0, "Vap",
                                                      "benzene"] <= 0.20 *
        m.fs.R101.outlet.flow_mol_phase_comp[0, "Vap", "benzene"])

    m.fs.product_flow = Constraint(
        expr=m.fs.F102.vap_outlet.flow_mol_phase_comp[0, "Vap",
                                                      "benzene"] >= 0.15)

    m.fs.product_purity = Constraint(expr=m.fs.purity >= 0.80)

    # Create a solver
    solver = SolverFactory('ipopt')
    solver.options = {'tol': 1e-6}
    solver.options = {'tol': 1e-6, 'max_iter': 5000}
    results = solver.solve(m, tee=True)

    # Print optimization results
    print()
    print("Optimal Solution")
    m.fs.operating_cost.display()
    m.fs.H101.heat_duty.display()
    m.fs.R101.heat_duty.display()
    m.fs.F101.heat_duty.display()
    m.fs.F102.heat_duty.display()

    # Print results
    print("M101 Outlet")
    m.fs.M101.outlet.display()

    print("H101 Outlet")
    m.fs.H101.outlet.display()

    print("R101 Outlet")
    m.fs.R101.outlet.display()

    print("F101")
    m.fs.F101.liq_outlet.display()
    m.fs.F101.vap_outlet.display()

    print("F102")
    m.fs.F102.liq_outlet.display()
    m.fs.F102.vap_outlet.display()

    print("Purge")
    m.fs.S101.purge.display()

    print("Recycle")
    m.fs.S101.recycle.display()

    print("Purity:", value(m.fs.purity))

    # For testing purposes
    return (m, results)
Esempio n. 6
0
def build_turbine_dyn():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": True, "time_set":[0,4]})
    m.fs.properties = iapws95.Iapws95ParameterBlock()
    m.fs.turb = HelmTurbineInletStage(default={"property_package": m.fs.properties})
    return m
Esempio n. 7
0
def main(m):
    m.fs = FlowsheetBlock(default={
        "dynamic": True,
        "time_set": [0, 3600],
        "time_units": pyunits.s
    })

    m.fs.gas_props = GasPhaseParameterBlock()
    m.fs.solid_props = SolidPhaseParameterBlock()
    m.fs.solid_rxns = HeteroReactionParameterBlock(
        default={
            "solid_property_package": m.fs.solid_props,
            "gas_property_package": m.fs.gas_props
        })

    m.fs.TGA = FixedBed0D(
        default={
            "energy_balance_type": EnergyBalanceType.none,
            "gas_property_package": m.fs.gas_props,
            "solid_property_package": m.fs.solid_props,
            "reaction_package": m.fs.solid_rxns
        })

    # Discretize time domain
    m.discretizer = TransformationFactory('dae.finite_difference')
    m.discretizer.apply_to(m, nfe=100, wrt=m.fs.time, scheme="BACKWARD")

    # Set reactor design conditions
    m.fs.TGA.bed_diameter.fix(1)  # diameter of the TGA reactor [m]
    m.fs.TGA.bed_height.fix(1)  # height of solids in the TGA reactor [m]

    # Set initial conditions of the solid phase
    m.fs.TGA.solids[0].particle_porosity.fix(0.20)
    m.fs.TGA.solids[0].mass_frac_comp['Fe2O3'].fix(0.45)
    m.fs.TGA.solids[0].mass_frac_comp['Fe3O4'].fix(0)
    m.fs.TGA.solids[0].mass_frac_comp['Al2O3'].fix(0.55)
    m.fs.TGA.solids[0].temperature.fix(1273.15)

    # Set conditions of the gas phase (this is all fixed as gas side assumption
    # is excess gas flowrate which means all state variables remain unchanged)
    for t in m.fs.time:
        m.fs.TGA.gas[t].temperature.fix(1273.15)
        m.fs.TGA.gas[t].pressure.fix(1.01325)  # 1atm
        m.fs.TGA.gas[t].mole_frac_comp['CO2'].fix(0.4)
        m.fs.TGA.gas[t].mole_frac_comp['H2O'].fix(0.5)
        m.fs.TGA.gas[t].mole_frac_comp['CH4'].fix(0.1)

    # Solver options
    optarg = {
        "bound_push": 1e-8,
        'halt_on_ampl_error': 'yes',
        'linear_solver': 'ma27'
    }

    t_start = time.time()  # Run start time

    m.fs.TGA.initialize()

    t_initialize = time.time()  # Initialization time

    solver = get_solver('ipopt', optarg)  # create solver

    initialize_by_time_element(m.fs, m.fs.time, solver=solver)
    solver.solve(m, tee=True)

    t_simulation = time.time()  # Simulation time

    print("\n")
    print("----------------------------------------------------------")
    print('Total initialization time: ', value(t_initialize - t_start), " s")
    print("----------------------------------------------------------")

    print("\n")
    print("----------------------------------------------------------")
    print('Total simulation time: ', value(t_simulation - t_start), " s")
    print("----------------------------------------------------------")

    return m
Esempio n. 8
0
def test_bad_option():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    with pytest.raises(KeyError):
        m.fs.unit = HeatExchanger(default={"I'm a bad option": "hot"})
Esempio n. 9
0
def test_same_name():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    with pytest.raises(NameError):
        m.fs.unit = HeatExchanger(default={"cold_side_name": "shell"})
    def test_Pdrop_fixed_per_unit_length(self):
        """Testing 0D-RO with PressureChangeType.fixed_per_unit_length option."""
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})

        m.fs.properties = props.NaClParameterBlock()

        m.fs.unit = ReverseOsmosis0D(
            default={
                "property_package": m.fs.properties,
                "has_pressure_change": True,
                "concentration_polarization_type":
                ConcentrationPolarizationType.calculated,
                "mass_transfer_coefficient":
                MassTransferCoefficient.calculated,
                "pressure_change_type":
                PressureChangeType.fixed_per_unit_length,
            })

        # fully specify system
        feed_flow_mass = 1
        feed_mass_frac_NaCl = 0.035
        feed_mass_frac_H2O = 1 - feed_mass_frac_NaCl
        feed_pressure = 50e5
        feed_temperature = 273.15 + 25
        membrane_area = 50
        length = 20
        A = 4.2e-12
        B = 3.5e-8
        pressure_atmospheric = 101325
        membrane_pressure_drop = 3e5

        m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "NaCl"].fix(
            feed_flow_mass * feed_mass_frac_NaCl)
        m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(
            feed_flow_mass * feed_mass_frac_H2O)
        m.fs.unit.inlet.pressure[0].fix(feed_pressure)
        m.fs.unit.inlet.temperature[0].fix(feed_temperature)
        m.fs.unit.area.fix(membrane_area)
        m.fs.unit.A_comp.fix(A)
        m.fs.unit.B_comp.fix(B)
        m.fs.unit.permeate.pressure[0].fix(pressure_atmospheric)

        m.fs.unit.channel_height.fix(0.002)
        m.fs.unit.spacer_porosity.fix(0.75)
        m.fs.unit.length.fix(length)
        m.fs.unit.dP_dx.fix(-membrane_pressure_drop / length)

        # test statistics
        assert number_variables(m) == 142
        assert number_total_constraints(m) == 112
        assert number_unused_variables(m) == 0

        # test degrees of freedom
        assert degrees_of_freedom(m) == 0

        # test scaling
        m.fs.properties.set_default_scaling("flow_mass_phase_comp",
                                            1,
                                            index=("Liq", "H2O"))
        m.fs.properties.set_default_scaling("flow_mass_phase_comp",
                                            1e2,
                                            index=("Liq", "NaCl"))

        calculate_scaling_factors(m)

        # check that all variables have scaling factors.
        # TODO: see aforementioned TODO on revisiting scaling and associated testing for property models.
        unscaled_var_list = list(
            unscaled_variables_generator(m.fs.unit, include_fixed=True))
        assert len(unscaled_var_list) == 0

        # test initialization
        initialization_tester(m, fail_on_warning=True)

        # test variable scaling
        badly_scaled_var_lst = list(badly_scaled_var_generator(m))
        assert badly_scaled_var_lst == []

        # test solve
        results = solver.solve(m, tee=True)

        # Check for optimal solution
        assert_optimal_termination(results)

        # test solution
        assert pytest.approx(-3.000e5, rel=1e-3) == value(m.fs.unit.deltaP[0])
        assert pytest.approx(4.562e-3, rel=1e-3) == value(
            m.fs.unit.flux_mass_phase_comp_avg[0, "Liq", "H2O"])
        assert pytest.approx(1.593e-6, rel=1e-3) == value(
            m.fs.unit.flux_mass_phase_comp_avg[0, "Liq", "NaCl"])
        assert pytest.approx(0.2281, rel=1e-3) == value(
            m.fs.unit.mixed_permeate[0].flow_mass_phase_comp["Liq", "H2O"])
        assert pytest.approx(7.963e-5, rel=1e-3) == value(
            m.fs.unit.mixed_permeate[0].flow_mass_phase_comp["Liq", "NaCl"])
        assert pytest.approx(41.96, rel=1e-3) == value(
            m.fs.unit.feed_side.properties_interface[
                0, 0.0].conc_mass_phase_comp["Liq", "NaCl"])
        assert pytest.approx(46.57, rel=1e-3) == value(
            m.fs.unit.feed_side.properties_out[0].conc_mass_phase_comp["Liq",
                                                                       "NaCl"])
        assert pytest.approx(49.94, rel=1e-3) == value(
            m.fs.unit.feed_side.properties_interface[
                0, 1.0].conc_mass_phase_comp["Liq", "NaCl"])
def test_initialize_by_time_element():
    horizon = 6
    time_set = [0, horizon]
    ntfe = 60 # For a finite element every six seconds
    ntcp = 2
    m = ConcreteModel(name='CSTR model for testing')
    m.fs = FlowsheetBlock(default={'dynamic': True,
                                   'time_set': time_set})

    m.fs.properties = AqueousEnzymeParameterBlock()
    m.fs.reactions = EnzymeReactionParameterBlock(
            default={'property_package': m.fs.properties})
    m.fs.cstr = CSTR(default={"property_package": m.fs.properties,
                              "reaction_package": m.fs.reactions,
                              "material_balance_type": MaterialBalanceType.componentTotal,
                              "energy_balance_type": EnergyBalanceType.enthalpyTotal,
                              "momentum_balance_type": MomentumBalanceType.none,
                              "has_heat_of_reaction": True})

    # Time discretization
    disc = TransformationFactory('dae.collocation')
    disc.apply_to(m, wrt=m.fs.time, nfe=ntfe, ncp=ntcp, scheme='LAGRANGE-RADAU')

    # Fix geometry variables
    m.fs.cstr.volume[0].fix(1.0)

    # Fix initial conditions:
    for p, j in m.fs.properties.phase_list*m.fs.properties.component_list:
        if j == 'Solvent':
            continue
        m.fs.cstr.control_volume.material_holdup[0, p, j].fix(0)

    # Fix inlet conditions
    # This is a huge hack because I didn't know that the proper way to
    # have multiple inlets to a CSTR was to use a mixer.
    # I 'combine' both my inlet streams before sending them to the CSTR.
    for t, j in m.fs.time*m.fs.properties.component_list:
        if t <= 2:
            if j == 'E':
                m.fs.cstr.inlet.conc_mol[t, j].fix(11.91*0.1/2.2)
            elif j == 'S':
                m.fs.cstr.inlet.conc_mol[t, j].fix(12.92*2.1/2.2)
            elif j != 'Solvent':
                m.fs.cstr.inlet.conc_mol[t, j].fix(0)
        elif t <= 4:
            if j == 'E':
                m.fs.cstr.inlet.conc_mol[t, j].fix(5.95*0.1/2.2)
            elif j == 'S':
                m.fs.cstr.inlet.conc_mol[t, j].fix(12.92*2.1/2.2)
            elif j != 'Solvent':
                m.fs.cstr.inlet.conc_mol[t, j].fix(0)
        else:
            if j == 'E':
                m.fs.cstr.inlet.conc_mol[t, j].fix(8.95*0.1/2.2)
            elif j == 'S':
                m.fs.cstr.inlet.conc_mol[t, j].fix(16.75*2.1/2.2)
            elif j != 'Solvent':
                m.fs.cstr.inlet.conc_mol[t, j].fix(0)

    m.fs.cstr.inlet.conc_mol[:, 'Solvent'].fix(1.)

    m.fs.cstr.inlet.flow_vol.fix(2.2)
    m.fs.cstr.inlet.temperature.fix(300)

    # Fix outlet conditions
    m.fs.cstr.outlet.flow_vol.fix(2.2)
    m.fs.cstr.outlet.temperature[m.fs.time.first()].fix(300)

    assert degrees_of_freedom(m) == 0

    initialize_by_time_element(m.fs, m.fs.time, solver=solver)

    assert degrees_of_freedom(m) == 0

    # Assert that the result looks how we expect
    assert m.fs.cstr.outlet.conc_mol[0, 'S'].value == 0
    assert abs(m.fs.cstr.outlet.conc_mol[2, 'S'].value - 11.389) < 1e-2
    assert abs(m.fs.cstr.outlet.conc_mol[4, 'P'].value - 0.2191) < 1e-3
    assert abs(m.fs.cstr.outlet.conc_mol[6, 'E'].value - 0.0327) < 1e-3
    assert abs(m.fs.cstr.outlet.temperature[6].value - 289.7) < 1

    # Assert that model is still fixed and deactivated as expected
    assert (
    m.fs.cstr.control_volume.material_holdup[m.fs.time.first(), 'aq', 'S'].fixed)

    for t in m.fs.time:
        if t != m.fs.time.first():
            assert (not 
    m.fs.cstr.control_volume.material_holdup[t, 'aq', 'S'].fixed)

            assert not m.fs.cstr.outlet.temperature[t].fixed
        assert (
    m.fs.cstr.control_volume.material_holdup_calculation[t, 'aq', 'C'].active)

        assert m.fs.cstr.control_volume.properties_out[t].active
        assert not m.fs.cstr.outlet.conc_mol[t, 'S'].fixed
        assert m.fs.cstr.inlet.conc_mol[t, 'S'].fixed

    # Assert that constraints are feasible after initialization
    for con in m.fs.component_data_objects(Constraint, active=True):
        assert value(con.body) - value(con.upper) < 1e-5
        assert value(con.lower) - value(con.body) < 1e-5

    results = solver.solve(m.fs)
    assert results.solver.termination_condition == TerminationCondition.optimal
    def test_CP_calculation_with_kf_fixed(self):
        """Testing 0D-RO with ConcentrationPolarizationType.calculated option enabled.
        This option makes use of an alternative constraint for the feed-side, membrane-interface concentration.
        Additionally, two more variables are created when this option is enabled: Kf - feed-channel
        mass transfer coefficients at the channel inlet and outlet.
        """
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})

        m.fs.properties = props.NaClParameterBlock()

        m.fs.unit = ReverseOsmosis0D(
            default={
                "property_package": m.fs.properties,
                "has_pressure_change": True,
                "concentration_polarization_type":
                ConcentrationPolarizationType.calculated,
                "mass_transfer_coefficient": MassTransferCoefficient.fixed,
            })

        # fully specify system
        feed_flow_mass = 1
        feed_mass_frac_NaCl = 0.035
        feed_pressure = 50e5
        feed_temperature = 273.15 + 25
        membrane_pressure_drop = 3e5
        membrane_area = 50
        A = 4.2e-12
        B = 3.5e-8
        pressure_atmospheric = 101325
        kf = 2e-5

        feed_mass_frac_H2O = 1 - feed_mass_frac_NaCl
        m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "NaCl"].fix(
            feed_flow_mass * feed_mass_frac_NaCl)
        m.fs.unit.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(
            feed_flow_mass * feed_mass_frac_H2O)
        m.fs.unit.inlet.pressure[0].fix(feed_pressure)
        m.fs.unit.inlet.temperature[0].fix(feed_temperature)
        m.fs.unit.deltaP.fix(-membrane_pressure_drop)
        m.fs.unit.area.fix(membrane_area)
        m.fs.unit.A_comp.fix(A)
        m.fs.unit.B_comp.fix(B)
        m.fs.unit.permeate.pressure[0].fix(pressure_atmospheric)
        m.fs.unit.Kf[0, 0.0, "NaCl"].fix(kf)
        m.fs.unit.Kf[0, 1.0, "NaCl"].fix(kf)

        # test statistics
        assert number_variables(m) == 125
        assert number_total_constraints(m) == 96
        assert number_unused_variables(
            m) == 7  # vars from property package parameters

        # test degrees of freedom
        assert degrees_of_freedom(m) == 0

        # test scaling
        m.fs.properties.set_default_scaling("flow_mass_phase_comp",
                                            1,
                                            index=("Liq", "H2O"))
        m.fs.properties.set_default_scaling("flow_mass_phase_comp",
                                            1e2,
                                            index=("Liq", "NaCl"))
        calculate_scaling_factors(m)

        # check that all variables have scaling factors.
        # TODO: Setting the "include_fixed" arg as True reveals
        #  unscaled vars that weren't being accounted for previously. However, calling the whole block (i.e.,
        #  m) shows that several NaCl property parameters are unscaled. For now, we are just interested in ensuring
        #  unit variables are scaled (hence, calling m.fs.unit) but might need to revisit scaling and associated
        #  testing for property models.

        unscaled_var_list = list(
            unscaled_variables_generator(m.fs.unit, include_fixed=True))
        assert len(unscaled_var_list) == 0

        # # test initialization
        initialization_tester(m, fail_on_warning=True)

        # test variable scaling
        badly_scaled_var_lst = list(badly_scaled_var_generator(m))
        assert badly_scaled_var_lst == []

        # test solve
        results = solver.solve(m)

        # Check for optimal solution
        assert_optimal_termination(results)

        # test solution
        assert pytest.approx(3.815e-3, rel=1e-3) == value(
            m.fs.unit.flux_mass_phase_comp_avg[0, "Liq", "H2O"])
        assert pytest.approx(1.668e-6, rel=1e-3) == value(
            m.fs.unit.flux_mass_phase_comp_avg[0, "Liq", "NaCl"])
        assert pytest.approx(0.1908, rel=1e-3) == value(
            m.fs.unit.mixed_permeate[0].flow_mass_phase_comp["Liq", "H2O"])
        assert pytest.approx(8.337e-5, rel=1e-3) == value(
            m.fs.unit.mixed_permeate[0].flow_mass_phase_comp["Liq", "NaCl"])
        assert pytest.approx(46.07, rel=1e-3) == value(
            m.fs.unit.feed_side.properties_interface[
                0, 0.0].conc_mass_phase_comp["Liq", "NaCl"])
        assert pytest.approx(44.34, rel=1e-3) == value(
            m.fs.unit.feed_side.properties_out[0].conc_mass_phase_comp["Liq",
                                                                       "NaCl"])
        assert pytest.approx(50.20, rel=1e-3) == value(
            m.fs.unit.feed_side.properties_interface[
                0, 1.0].conc_mass_phase_comp["Liq", "NaCl"])
Esempio n. 13
0
def test_costing_distillation_solve():
    m = pyo.ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.get_costing()
    m.fs.costing.CE_index = 550
    # create a unit model and variables
    m.fs.unit = pyo.Block()
    m.fs.unit.heat_duty = pyo.Var(initialize=1e6,
                                  units=pyo.units.BTU / pyo.units.hr)
    m.fs.unit.pressure = pyo.Var(initialize=1e5, units=pyo.units.psi)
    m.fs.unit.diameter = pyo.Var(initialize=10,
                                 domain=pyo.NonNegativeReals,
                                 units=pyo.units.foot)
    m.fs.unit.length = pyo.Var(initialize=10,
                               domain=pyo.NonNegativeReals,
                               units=pyo.units.foot)
    # create costing block
    m.fs.unit.costing = pyo.Block()

    cs.vessel_costing(m.fs.unit.costing,
                      alignment='vertical',
                      weight_limit='option2',
                      L_D_range='option2',
                      PL=True,
                      plates=True,
                      number_tray=100,
                      ref_parameter_diameter=m.fs.unit.diameter,
                      ref_parameter_length=m.fs.unit.length)
    # pressure design and shell thickness from Example 22.13 Product and
    # Process Design Principless
    m.fs.unit.heat_duty.fix(18390000)  # Btu/hr
    m.fs.unit.pressure.fix(123 + 14.6959)  # psia
    # pressure design minimum thickness tp = 0.582 in
    # vessel is vertical + quite tall the tower is subject to wind load,
    # and earthquake. Assume wall thickness of 1.25 in.
    # The additional wall thickness at the bottom of the tower is 0.889 in
    # average thickness is 1.027, plus corrosion allowance of 1/8
    # 1.152 in, therefore steel plate thickness is 1.250 (ts)
    m.fs.unit.costing.shell_thickness.set_value(1.250)  # inches
    m.fs.unit.diameter.fix(10)  # ft
    m.fs.unit.length.fix(212)  # ft
    m.fs.unit.costing.number_trays.set_value(100)

    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=False)
    # Check for optimal solution
    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) == 636959.6929)  # Example 22.13 Ref Book
    assert (pytest.approx(pyo.value(m.fs.unit.costing.base_cost_platf_ladders),
                          abs=1e-2) == 97542.9005)  # Example 22.13 Ref Book
    assert (pytest.approx(pyo.value(m.fs.unit.costing.purchase_cost_trays),
                          abs=1e-2) == 293006.086)  # Example 22.13 Ref Book
    assert (pytest.approx(pyo.value(m.fs.unit.costing.purchase_cost),
                          abs=1e-2) == 1100958.9396)  # Example 22.13 Ref Book
def main():

    # ---------------------------------------------------------------------
    # Build model

    # Create a concrete model
    m = ConcreteModel()

    # Create a steady-state flowsheet
    m.fs = FlowsheetBlock(default={"dynamic": False})

    # Set up thermo-physical and reaction properties
    m.fs.gas_properties = GasPhaseThermoParameterBlock()
    m.fs.solid_properties = SolidPhaseThermoParameterBlock()

    m.fs.hetero_reactions = HeteroReactionParameterBlock(
        default={
            "solid_property_package": m.fs.solid_properties,
            "gas_property_package": m.fs.gas_properties
        })

    # Build the BFB in the flowsheet
    m.fs.BFB = BubblingFluidizedBed(
        default={
            "flow_type": "co_current",
            "finite_elements": 5,
            "transformation_method": "dae.collocation",
            "gas_phase_config": {
                "property_package": m.fs.gas_properties
            },
            "solid_phase_config": {
                "property_package": m.fs.solid_properties,
                "reaction_package": m.fs.hetero_reactions
            }
        })

    # ---------------------------------------------------------------------
    # Set design and operating variables of the BFB model

    # Fix design variables
    m.fs.BFB.number_orifice.fix(2500)  # [-]
    m.fs.BFB.bed_diameter.fix(6.5)  # m
    m.fs.BFB.bed_height.fix(5)  # m

    # Fix inlet port variables for gas and solid
    m.fs.BFB.gas_inlet.flow_mol[0].fix(272.81)  # mol/s
    m.fs.BFB.gas_inlet.temperature[0].fix(373)  # K
    m.fs.BFB.gas_inlet.pressure[0].fix(1.86)  # bar
    m.fs.BFB.gas_inlet.mole_frac_comp[0, "CO2"].fix(0.4772)
    m.fs.BFB.gas_inlet.mole_frac_comp[0, "H2O"].fix(0.0646)
    m.fs.BFB.gas_inlet.mole_frac_comp[0, "CH4"].fix(0.4582)

    m.fs.BFB.solid_inlet.flow_mass[0].fix(1230)  # kg/s
    m.fs.BFB.solid_inlet.temperature[0].fix(1186)  # K
    m.fs.BFB.solid_inlet.mass_frac_comp[0, "Fe2O3"].fix(0.45)
    m.fs.BFB.solid_inlet.mass_frac_comp[0, "Fe3O4"].fix(1e-9)
    m.fs.BFB.solid_inlet.mass_frac_comp[0, "Al2O3"].fix(0.55)

    # ---------------------------------------------------------------------
    # Initialize reactor

    t_start = time.time()  # Run start time

    # State arguments for initializing property state blocks
    # Bubble and gas_emulsion temperatures are initialized at solid
    # temperature because thermal mass of solid >> thermal mass of gas
    blk = m.fs.BFB
    gas_phase_state_args = {
        'flow_mol': blk.gas_inlet.flow_mol[0].value,
        'temperature': blk.solid_inlet.temperature[0].value,
        'pressure': blk.gas_inlet.pressure[0].value,
        'mole_frac': {
            'CH4': blk.gas_inlet.mole_frac_comp[0, 'CH4'].value,
            'CO2': blk.gas_inlet.mole_frac_comp[0, 'CO2'].value,
            'H2O': blk.gas_inlet.mole_frac_comp[0, 'H2O'].value
        }
    }
    solid_phase_state_args = {
        'flow_mass': blk.solid_inlet.flow_mass[0].value,
        'temperature': blk.solid_inlet.temperature[0].value,
        'mass_frac': {
            'Fe2O3': blk.solid_inlet.mass_frac_comp[0, 'Fe2O3'].value,
            'Fe3O4': blk.solid_inlet.mass_frac_comp[0, 'Fe3O4'].value,
            'Al2O3': blk.solid_inlet.mass_frac_comp[0, 'Al2O3'].value
        }
    }

    m.fs.BFB.initialize(outlvl=idaeslog.INFO,
                        gas_phase_state_args=gas_phase_state_args,
                        solid_phase_state_args=solid_phase_state_args)

    t_initialize = time.time()  # Initialization time

    # ---------------------------------------------------------------------
    # Final solve

    # Create a solver
    solver = SolverFactory('ipopt')
    solver.solve(m.fs.BFB, tee=True)

    t_simulation = time.time()  # Simulation time

    print("\n")
    print("----------------------------------------------------------")
    print('Total initialization time: ', value(t_initialize - t_start), " s")
    print("----------------------------------------------------------")

    print("\n")
    print("----------------------------------------------------------")
    print('Total simulation time: ', value(t_simulation - t_start), " s")
    print("----------------------------------------------------------")

    return m
Esempio n. 15
0
    def test_T368_P1_x5(self):
        m = ConcreteModel()

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

        m.fs.props = BT_PR.BTParameterBlock(
                default={'valid_phase': ('Vap', 'Liq')})

        m.fs.state = m.fs.props.build_state_block(
                default={'defined_state': True})

        m.fs.state.flow_mol.fix(100)
        m.fs.state.mole_frac_comp["benzene"].fix(0.5)
        m.fs.state.mole_frac_comp["toluene"].fix(0.5)
        m.fs.state.temperature.fix(368)
        m.fs.state.pressure.fix(1e5)

        # Trigger build of enthalpy and entropy
        m.fs.state.enth_mol_phase
        m.fs.state.entr_mol_phase

        m.fs.state.initialize()

        solver.solve(m)

        assert pytest.approx(value(m.fs.state._teq), 1e-5) == 368
        assert 0.003504 == pytest.approx(
                value(m.fs.state.compress_fact_phase["Liq"]), 1e-5)
        assert 0.97 == pytest.approx(
                value(m.fs.state.compress_fact_phase["Vap"]), 1e-5)
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "benzene"]),
                1e-5) == 1.492049
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "toluene"]),
                1e-5) == 0.621563
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "benzene"]),
                1e-5) == 0.97469
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "toluene"]),
                1e-5) == 0.964642

        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "benzene"]),
                1e-5) == 0.4012128
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "toluene"]),
                1e-5) == 0.5987872
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "benzene"]),
                1e-5) == 0.6141738
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "toluene"]),
                1e-5) == 0.3858262

        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Liq"]), 1e-5) == 38235.1
        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Vap"]), 1e-5) == 77155.4
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Liq"]), 1e-5) == -364.856
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Vap"]), 1e-5) == -267.892
Esempio n. 16
0
    def btx(self):
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})

        # As we lack other example prop packs with units, take the generic
        # BT-PR package and change the base units
        configuration2 = {
            # Specifying components
            "components": {
                'benzene': {
                    "type": Component,
                    "enth_mol_ig_comp": RPP,
                    "entr_mol_ig_comp": RPP,
                    "pressure_sat_comp": RPP,
                    "phase_equilibrium_form": {
                        ("Vap", "Liq"): log_fugacity
                    },
                    "parameter_data": {
                        "mw": (78.1136E-3, pyunits.kg / pyunits.mol),  # [1]
                        "pressure_crit": (48.9e5, pyunits.Pa),  # [1]
                        "temperature_crit": (562.2, pyunits.K),  # [1]
                        "omega":
                        0.212,  # [1]
                        "cp_mol_ig_comp_coeff": {
                            'A': (-3.392E1,
                                  pyunits.J / pyunits.mol / pyunits.K),  # [1]
                            'B':
                            (4.739E-1, pyunits.J / pyunits.mol / pyunits.K**2),
                            'C': (-3.017E-4,
                                  pyunits.J / pyunits.mol / pyunits.K**3),
                            'D':
                            (7.130E-8, pyunits.J / pyunits.mol / pyunits.K**4)
                        },
                        "enth_mol_form_vap_comp_ref":
                        (82.9e3, pyunits.J / pyunits.mol),  # [3]
                        "entr_mol_form_vap_comp_ref":
                        (-269, pyunits.J / pyunits.mol / pyunits.K),  # [3]
                        "pressure_sat_comp_coeff": {
                            'A': (-6.98273, None),  # [1]
                            'B': (1.33213, None),
                            'C': (-2.62863, None),
                            'D': (-3.33399, None)
                        }
                    }
                },
                'toluene': {
                    "type": Component,
                    "enth_mol_ig_comp": RPP,
                    "entr_mol_ig_comp": RPP,
                    "pressure_sat_comp": RPP,
                    "phase_equilibrium_form": {
                        ("Vap", "Liq"): log_fugacity
                    },
                    "parameter_data": {
                        "mw": (92.1405E-3, pyunits.kg / pyunits.mol),  # [1]
                        "pressure_crit": (41e5, pyunits.Pa),  # [1]
                        "temperature_crit": (591.8, pyunits.K),  # [1]
                        "omega":
                        0.263,  # [1]
                        "cp_mol_ig_comp_coeff": {
                            'A': (-2.435E1,
                                  pyunits.J / pyunits.mol / pyunits.K),  # [1]
                            'B':
                            (5.125E-1, pyunits.J / pyunits.mol / pyunits.K**2),
                            'C': (-2.765E-4,
                                  pyunits.J / pyunits.mol / pyunits.K**3),
                            'D':
                            (4.911E-8, pyunits.J / pyunits.mol / pyunits.K**4)
                        },
                        "enth_mol_form_vap_comp_ref":
                        (50.1e3, pyunits.J / pyunits.mol),  # [3]
                        "entr_mol_form_vap_comp_ref":
                        (-321, pyunits.J / pyunits.mol / pyunits.K),  # [3]
                        "pressure_sat_comp_coeff": {
                            'A': (-7.28607, None),  # [1]
                            'B': (1.38091, None),
                            'C': (-2.83433, None),
                            'D': (-2.79168, None)
                        }
                    }
                }
            },
            # Specifying phases
            "phases": {
                'Liq': {
                    "type": LiquidPhase,
                    "equation_of_state": Cubic,
                    "equation_of_state_options": {
                        "type": CubicType.PR
                    }
                },
                'Vap': {
                    "type": VaporPhase,
                    "equation_of_state": Cubic,
                    "equation_of_state_options": {
                        "type": CubicType.PR
                    }
                }
            },
            # Set base units of measurement
            "base_units": {
                "time": pyunits.s,
                "length": pyunits.m,
                "mass": pyunits.t,
                "amount": pyunits.mol,
                "temperature": pyunits.degR
            },
            # Specifying state definition
            "state_definition": FTPx,
            "state_bounds": {
                "flow_mol": (0, 100, 1000, pyunits.mol / pyunits.s),
                "temperature": (273.15, 300, 500, pyunits.K),
                "pressure": (5e4, 1e5, 1e6, pyunits.Pa)
            },
            "pressure_ref": (101325, pyunits.Pa),
            "temperature_ref": (298.15, pyunits.K),
            # Defining phase equilibria
            "phases_in_equilibrium": [("Vap", "Liq")],
            "phase_equilibrium_state": {
                ("Vap", "Liq"): smooth_VLE
            },
            "bubble_dew_method": LogBubbleDew,
            "parameter_data": {
                "PR_kappa": {
                    ("benzene", "benzene"): 0.000,
                    ("benzene", "toluene"): 0.000,
                    ("toluene", "benzene"): 0.000,
                    ("toluene", "toluene"): 0.000
                }
            }
        }

        m.fs.properties = GenericParameterBlock(default=configuration)
        m.fs.properties2 = GenericParameterBlock(default=configuration2)

        m.fs.unit = HeatExchanger(
            default={
                "shell": {
                    "property_package": m.fs.properties
                },
                "tube": {
                    "property_package": m.fs.properties2
                },
                "flow_pattern": HeatExchangerFlowPattern.cocurrent
            })

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

        m.fs.unit.inlet_2.flow_mol[0].fix(1)  # mol/s
        m.fs.unit.inlet_2.temperature[0].fix(540)  # degR
        m.fs.unit.inlet_2.pressure[0].fix(101.325)  # kPa
        m.fs.unit.inlet_2.mole_frac_comp[0, "benzene"].fix(0.5)
        m.fs.unit.inlet_2.mole_frac_comp[0, "toluene"].fix(0.5)

        m.fs.unit.area.fix(1)
        m.fs.unit.overall_heat_transfer_coefficient.fix(100)

        m.fs.unit.side_2.scaling_factor_pressure = 1

        return m
Esempio n. 17
0
    def test_T376_P1_x2(self):
        m = ConcreteModel()

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

        m.fs.props = BT_PR.BTParameterBlock(
                default={'valid_phase': ('Vap', 'Liq')})

        m.fs.state = m.fs.props.build_state_block(
                default={'defined_state': True})

        m.fs.state.flow_mol.fix(100)
        m.fs.state.mole_frac_comp["benzene"].fix(0.2)
        m.fs.state.mole_frac_comp["toluene"].fix(0.8)
        m.fs.state.temperature.fix(376)
        m.fs.state.pressure.fix(1e5)

        # Trigger build of enthalpy and entropy
        m.fs.state.enth_mol_phase
        m.fs.state.entr_mol_phase

        m.fs.state.initialize()

        solver.solve(m)

        assert pytest.approx(value(m.fs.state._teq), 1e-5) == 376
        assert 0.00361333 == pytest.approx(
                value(m.fs.state.compress_fact_phase["Liq"]), 1e-5)
        assert 0.968749 == pytest.approx(
                value(m.fs.state.compress_fact_phase["Vap"]), 1e-5)
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "benzene"]),
                1e-5) == 1.8394188
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "toluene"]),
                1e-5) == 0.7871415
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "benzene"]),
                1e-5) == 0.9763608
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "toluene"]),
                1e-5) == 0.9663611

        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "benzene"]),
                1e-5) == 0.17342
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "toluene"]),
                1e-5) == 0.82658
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "benzene"]),
                1e-5) == 0.3267155
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "toluene"]),
                1e-5) == 0.6732845

        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Liq"]), 1e-5) == 31535.8
        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Vap"]), 1e-5) == 69175.3
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Liq"]), 1e-5) == -372.869
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Vap"]), 1e-5) == -278.766
Esempio n. 18
0
def create_model(steady_state=True,
                 time_set=[0, 3],
                 time_units=pyo.units.s,
                 nfe=5,
                 calc_integ=True,
                 form=PIDForm.standard):
    """ Create a test model and solver

    Args:
        steady_state (bool): If True, create a steady state model, otherwise
            create a dynamic model
        time_set (list): The begining and end point of the time domain
        time_units (Pyomo Unit object): Units of time domain
        nfe (int): Number of finite elements argument for the DAE
            transformation.
        calc_integ (bool): If True, calculate in the initial condition for
            the integral term, else use a fixed variable (fs.ctrl.err_i0),
            False is the better option if you have a value from a previous
            time period
        form: whether the equations are written in the standard or velocity
            form

    Returns
        (tuple): (ConcreteModel, Solver)
    """
    if steady_state:
        fs_cfg = {"dynamic": False}
        model_name = "Steam Tank, Steady State"
    else:
        fs_cfg = {
            "dynamic": True,
            "time_set": time_set,
            "time_units": time_units
        }
        model_name = "Steam Tank, Dynamic"

    m = pyo.ConcreteModel(name=model_name)
    m.fs = FlowsheetBlock(default=fs_cfg)
    # Create a property parameter block
    m.fs.prop_water = iapws95.Iapws95ParameterBlock(
        default={"phase_presentation": iapws95.PhaseType.LG})
    # Create the valve and tank models
    m.fs.valve_1 = SteamValve(
        default={
            "dynamic": False,
            "has_holdup": False,
            "material_balance_type": MaterialBalanceType.componentTotal,
            "property_package": m.fs.prop_water
        })
    m.fs.tank = Heater(
        default={
            "has_holdup": True,
            "material_balance_type": MaterialBalanceType.componentTotal,
            "property_package": m.fs.prop_water
        })
    m.fs.valve_2 = SteamValve(
        default={
            "dynamic": False,
            "has_holdup": False,
            "material_balance_type": MaterialBalanceType.componentTotal,
            "property_package": m.fs.prop_water
        })

    # Connect the models
    m.fs.v1_to_t = Arc(source=m.fs.valve_1.outlet, destination=m.fs.tank.inlet)
    m.fs.t_to_v2 = Arc(source=m.fs.tank.outlet, destination=m.fs.valve_2.inlet)

    # The control volume block doesn't assume the two phases are in equilibrium
    # by default, so I'll make that assumption here, I don't actually expect
    # liquid to form but who knows. The phase_fraction in the control volume is
    # volumetric phase fraction hence the densities.
    @m.fs.tank.Constraint(m.fs.time)
    def vol_frac_vap(b, t):
        return (b.control_volume.properties_out[t].phase_frac["Vap"] *
                b.control_volume.properties_out[t].dens_mol /
                b.control_volume.properties_out[t].dens_mol_phase["Vap"]) == (
                    b.control_volume.phase_fraction[t, "Vap"])

    # Add the stream constraints and do the DAE transformation
    pyo.TransformationFactory('network.expand_arcs').apply_to(m.fs)
    if not steady_state:
        pyo.TransformationFactory('dae.finite_difference').apply_to(
            m.fs, nfe=nfe, wrt=m.fs.time, scheme='BACKWARD')

    # Fix the derivative variables to zero at time 0 (steady state assumption)
    m.fs.fix_initial_conditions()

    # A tank pressure reference that's directly time-indexed
    m.fs.tank_pressure = pyo.Reference(
        m.fs.tank.control_volume.properties_out[:].pressure)

    # Add a controller
    m.fs.ctrl = PIDBlock(
        default={
            "pv": m.fs.tank_pressure,
            "output": m.fs.valve_1.valve_opening,
            "upper": 1.0,
            "lower": 0.0,
            "calculate_initial_integral": calc_integ,
            "pid_form": form
        })
    m.fs.ctrl.deactivate()  # Don't want controller turned on by default

    # Fix the input variables
    m.fs.valve_1.inlet.enth_mol.fix(50000)
    m.fs.valve_1.inlet.pressure.fix(5e5)
    m.fs.valve_2.outlet.pressure.fix(101325)
    m.fs.valve_1.Cv.fix(0.001)
    m.fs.valve_2.Cv.fix(0.001)
    m.fs.valve_1.valve_opening.fix(1)
    m.fs.valve_2.valve_opening.fix(1)
    m.fs.tank.heat_duty.fix(0)
    m.fs.tank.control_volume.volume.fix(2.0)
    m.fs.ctrl.gain.fix(1e-6)
    m.fs.ctrl.time_i.fix(0.1)
    m.fs.ctrl.time_d.fix(0.1)
    m.fs.ctrl.setpoint.fix(3e5)

    # Initialize the model
    solver = pyo.SolverFactory("ipopt")
    solver.options = {'tol': 1e-6, 'linear_solver': "ma27", 'max_iter': 100}
    for t in m.fs.time:
        m.fs.valve_1.inlet.flow_mol = 100  # initial guess on flow
    # simple initialize
    m.fs.valve_1.initialize(outlvl=1)
    _set_port(m.fs.tank.inlet, m.fs.valve_1.outlet)
    m.fs.tank.initialize(outlvl=1)
    _set_port(m.fs.valve_2.inlet, m.fs.tank.outlet)
    m.fs.valve_2.initialize(outlvl=1)
    solver.solve(m, tee=True)

    # Return the model and solver
    return m, solver
Esempio n. 19
0
def test_seawater_data():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={'dynamic': False})
    m.fs.properties = DSPMDEParameterBlock(
        default={
            "solute_list": ["Ca_2+", "SO4_2-", "Na_+", "Cl_-", "Mg_2+"],
            "diffusivity_data": {
                ("Liq", "Ca_2+"): 0.792e-9,
                ("Liq", "SO4_2-"): 1.06e-9,
                ("Liq", "Na_+"): 1.33e-9,
                ("Liq", "Cl_-"): 2.03e-9,
                ("Liq", "Mg_2+"): 0.706e-9
            },
            "mw_data": {
                "H2O": 18e-3,
                "Na_+": 23e-3,
                "Ca_2+": 40e-3,
                "Mg_2+": 24e-3,
                "Cl_-": 35e-3,
                "SO4_2-": 96e-3
            },
            "stokes_radius_data": {
                "Na_+": 0.184e-9,
                "Ca_2+": 0.309e-9,
                "Mg_2+": 0.347e-9,
                "Cl_-": 0.121e-9,
                "SO4_2-": 0.230e-9
            },
            "charge": {
                "Na_+": 1,
                "Ca_2+": 2,
                "Mg_2+": 2,
                "Cl_-": -1,
                "SO4_2-": -2
            },
        })

    m.fs.stream = stream = m.fs.properties.build_state_block(
        [0], default={'defined_state': True})

    mass_flow_in = 1 * pyunits.kg / pyunits.s
    feed_mass_frac = {
        'Na_+': 11122e-6,
        'Ca_2+': 382e-6,
        'Mg_2+': 1394e-6,
        'SO4_2-': 2136e-6,
        'Cl_-': 20300e-6
    }
    for ion, x in feed_mass_frac.items():
        mol_comp_flow = x * pyunits.kg / pyunits.kg * mass_flow_in / stream[
            0].mw_comp[ion]

        stream[0].flow_mol_phase_comp['Liq', ion].fix(mol_comp_flow)

    H2O_mass_frac = 1 - sum(x for x in feed_mass_frac.values())
    H2O_mol_comp_flow = H2O_mass_frac * pyunits.kg / pyunits.kg * mass_flow_in / stream[
        0].mw_comp['H2O']

    stream[0].flow_mol_phase_comp['Liq', 'H2O'].fix(H2O_mol_comp_flow)
    stream[0].temperature.fix(298.15)
    stream[0].pressure.fix(101325)

    stream[0].assert_electroneutrality(tol=1e-2)

    metadata = m.fs.properties.get_metadata().properties
    for v_name in metadata:
        getattr(stream[0], v_name)
    assert stream[0].is_property_constructed('conc_mol_phase_comp')

    assert_units_consistent(m)

    check_dof(m, fail_flag=True)

    m.fs.properties.set_default_scaling('flow_mol_phase_comp',
                                        1,
                                        index=('Liq', 'H2O'))
    m.fs.properties.set_default_scaling('flow_mol_phase_comp',
                                        1,
                                        index=('Liq', 'Na_+'))
    m.fs.properties.set_default_scaling('flow_mol_phase_comp',
                                        1,
                                        index=('Liq', 'Cl_-'))
    m.fs.properties.set_default_scaling('flow_mol_phase_comp',
                                        1e1,
                                        index=('Liq', 'Ca_2+'))
    m.fs.properties.set_default_scaling('flow_mol_phase_comp',
                                        1e1,
                                        index=('Liq', 'SO4_2-'))
    m.fs.properties.set_default_scaling('flow_mol_phase_comp',
                                        1,
                                        index=('Liq', 'Mg_2+'))
    calculate_scaling_factors(m)

    # check if any variables are badly scaled
    badly_scaled_var_list = list(badly_scaled_var_generator(m))
    assert len(badly_scaled_var_list) == 0

    stream.initialize()

    # check if any variables are badly scaled
    badly_scaled_var_list = list(badly_scaled_var_generator(m))
    assert len(badly_scaled_var_list) == 0

    results = solver.solve(m)
    assert_optimal_termination(results)

    assert value(stream[0].flow_vol_phase['Liq']) == pytest.approx(0.001,
                                                                   rel=1e-3)
    assert value(stream[0].flow_mol_phase_comp['Liq', 'H2O']) == pytest.approx(
        53.59256, rel=1e-3)
    assert value(stream[0].flow_mol_phase_comp['Liq',
                                               'Na_+']) == pytest.approx(
                                                   0.4836, rel=1e-3)
    assert value(stream[0].flow_mol_phase_comp['Liq',
                                               'Ca_2+']) == pytest.approx(
                                                   0.00955, rel=1e-3)
    assert value(stream[0].flow_mol_phase_comp['Liq',
                                               'Mg_2+']) == pytest.approx(
                                                   0.05808, rel=1e-3)
    assert value(stream[0].flow_mol_phase_comp['Liq',
                                               'Cl_-']) == pytest.approx(
                                                   0.58, rel=1e-3)
    assert value(stream[0].flow_mol_phase_comp['Liq',
                                               'SO4_2-']) == pytest.approx(
                                                   0.02225, rel=1e-3)
    assert value(stream[0].dens_mass_phase['Liq']) == pytest.approx(1000,
                                                                    rel=1e-3)
    assert value(stream[0].pressure_osm) == pytest.approx(28.593e5, rel=1e-3)
    assert value(stream[0].flow_vol) == pytest.approx(0.001, rel=1e-3)

    assert value(
        sum(stream[0].conc_mass_phase_comp['Liq', j]
            for j in m.fs.properties.solute_set)) == pytest.approx(35.334,
                                                                   rel=1e-3)
    assert value(
        sum(stream[0].mass_frac_phase_comp['Liq', j]
            for j in m.fs.properties.solute_set)) == pytest.approx(35334e-6,
                                                                   rel=1e-3)
    assert value(
        sum(stream[0].mass_frac_phase_comp['Liq', j]
            for j in m.fs.properties.component_list)) == pytest.approx(
                1, rel=1e-3)
    assert value(
        sum(stream[0].mole_frac_phase_comp['Liq', j]
            for j in m.fs.properties.component_list)) == pytest.approx(
                1, rel=1e-3)

    assert value(stream[0].conc_mol_phase_comp['Liq',
                                               'Na_+']) == pytest.approx(
                                                   483.565, rel=1e-3)
    assert value(stream[0].conc_mol_phase_comp['Liq',
                                               'Cl_-']) == pytest.approx(
                                                   580, rel=1e-3)
    assert value(stream[0].conc_mol_phase_comp['Liq',
                                               'Ca_2+']) == pytest.approx(
                                                   9.55, rel=1e-3)
    assert value(stream[0].conc_mol_phase_comp['Liq',
                                               'SO4_2-']) == pytest.approx(
                                                   22.25, rel=1e-3)
    assert value(stream[0].conc_mol_phase_comp['Liq',
                                               'Mg_2+']) == pytest.approx(
                                                   58.08, rel=1e-3)

    assert value(stream[0].conc_mass_phase_comp['Liq',
                                                'Na_+']) == pytest.approx(
                                                    11.122, rel=1e-3)
    assert value(stream[0].conc_mass_phase_comp['Liq',
                                                'Cl_-']) == pytest.approx(
                                                    20.3, rel=1e-3)
    assert value(stream[0].conc_mass_phase_comp['Liq',
                                                'Ca_2+']) == pytest.approx(
                                                    0.382, rel=1e-3)
    assert value(stream[0].conc_mass_phase_comp['Liq',
                                                'SO4_2-']) == pytest.approx(
                                                    2.136, rel=1e-3)
    assert value(stream[0].conc_mass_phase_comp['Liq',
                                                'Mg_2+']) == pytest.approx(
                                                    1.394, rel=1e-3)

    assert value(stream[0].mole_frac_phase_comp['Liq',
                                                'Na_+']) == pytest.approx(
                                                    8.833e-3, rel=1e-3)
    assert value(stream[0].mole_frac_phase_comp['Liq',
                                                'Cl_-']) == pytest.approx(
                                                    1.059e-2, rel=1e-3)
    assert value(stream[0].mole_frac_phase_comp['Liq',
                                                'Ca_2+']) == pytest.approx(
                                                    1.744e-4, rel=1e-3)
    assert value(stream[0].mole_frac_phase_comp['Liq',
                                                'SO4_2-']) == pytest.approx(
                                                    4.064e-4, rel=1e-3)
    assert value(stream[0].mole_frac_phase_comp['Liq',
                                                'Mg_2+']) == pytest.approx(
                                                    1.061e-3, rel=1e-3)

    assert value(stream[0].mass_frac_phase_comp['Liq',
                                                'Na_+']) == pytest.approx(
                                                    1.112e-2, rel=1e-3)
    assert value(stream[0].mass_frac_phase_comp['Liq',
                                                'Cl_-']) == pytest.approx(
                                                    2.03e-2, rel=1e-3)
    assert value(stream[0].mass_frac_phase_comp['Liq',
                                                'Ca_2+']) == pytest.approx(
                                                    3.82e-4, rel=1e-3)
    assert value(stream[0].mass_frac_phase_comp['Liq',
                                                'SO4_2-']) == pytest.approx(
                                                    2.136e-3, rel=1e-3)
    assert value(stream[0].mass_frac_phase_comp['Liq',
                                                'Mg_2+']) == pytest.approx(
                                                    1.394e-3, rel=1e-3)
Esempio n. 20
0
from VLE data to compute the activity coefficients.

Author: Jaffer Ghouse
"""
import pytest
from pyomo.environ import ConcreteModel

from idaes.core import FlowsheetBlock
from idaes.property_models.activity_coeff_models.BTX_activity_coeff_VLE \
    import BTXParameterBlock
from idaes.ui.report import degrees_of_freedom

# -----------------------------------------------------------------------------
# Create a flowsheet for test
m = ConcreteModel()
m.fs = FlowsheetBlock(default={"dynamic": False})

# vapor-liquid (NRTL)
m.fs.properties_NRTL_vl = BTXParameterBlock(default={
    "valid_phase": ('Liq', 'Vap'),
    "activity_coeff_model": 'NRTL'
})
m.fs.state_block_NRTL_vl = m.fs.properties_NRTL_vl.state_block_class(
    default={
        "parameters": m.fs.properties_NRTL_vl,
        "defined_state": True
    })

# liquid only (NRTL)
m.fs.properties_NRTL_l = BTXParameterBlock(default={
    "valid_phase": 'Liq',
Esempio n. 21
0
def build(erd_type=None):
    # flowsheet set up
    m = ConcreteModel()
    m.db = Database()
    m.erd_type = erd_type

    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.prop_prtrt = prop_ZO.WaterParameterBlock(
        default={"solute_list": ["tds", "tss"]}
    )
    density = 1023.5 * pyunits.kg / pyunits.m**3
    m.fs.prop_prtrt.dens_mass_default = density
    m.fs.prop_psttrt = prop_ZO.WaterParameterBlock(default={"solute_list": ["tds"]})
    m.fs.prop_desal = prop_SW.SeawaterParameterBlock()

    # block structure
    prtrt = m.fs.pretreatment = Block()
    desal = m.fs.desalination = Block()
    psttrt = m.fs.posttreatment = Block()

    # unit models
    m.fs.feed = FeedZO(default={"property_package": m.fs.prop_prtrt})
    # pretreatment
    prtrt.intake = SWOnshoreIntakeZO(default={"property_package": m.fs.prop_prtrt})
    prtrt.ferric_chloride_addition = ChemicalAdditionZO(
        default={
            "property_package": m.fs.prop_prtrt,
            "database": m.db,
            "process_subtype": "ferric_chloride",
        }
    )
    prtrt.chlorination = ChlorinationZO(
        default={"property_package": m.fs.prop_prtrt, "database": m.db}
    )
    prtrt.static_mixer = StaticMixerZO(
        default={"property_package": m.fs.prop_prtrt, "database": m.db}
    )
    prtrt.storage_tank_1 = StorageTankZO(
        default={"property_package": m.fs.prop_prtrt, "database": m.db}
    )
    prtrt.media_filtration = MediaFiltrationZO(
        default={"property_package": m.fs.prop_prtrt, "database": m.db}
    )
    prtrt.backwash_handling = BackwashSolidsHandlingZO(
        default={"property_package": m.fs.prop_prtrt, "database": m.db}
    )
    prtrt.anti_scalant_addition = ChemicalAdditionZO(
        default={
            "property_package": m.fs.prop_prtrt,
            "database": m.db,
            "process_subtype": "anti-scalant",
        }
    )
    prtrt.cartridge_filtration = CartridgeFiltrationZO(
        default={"property_package": m.fs.prop_prtrt, "database": m.db}
    )

    # desalination
    desal.P1 = Pump(default={"property_package": m.fs.prop_desal})
    desal.RO = ReverseOsmosis0D(
        default={
            "property_package": m.fs.prop_desal,
            "has_pressure_change": True,
            "pressure_change_type": PressureChangeType.calculated,
            "mass_transfer_coefficient": MassTransferCoefficient.calculated,
            "concentration_polarization_type": ConcentrationPolarizationType.calculated,
        }
    )
    desal.RO.width.setub(5000)
    desal.RO.area.setub(20000)
    if erd_type == "pressure_exchanger":
        desal.S1 = Separator(
            default={"property_package": m.fs.prop_desal, "outlet_list": ["P1", "PXR"]}
        )
        desal.M1 = Mixer(
            default={
                "property_package": m.fs.prop_desal,
                "momentum_mixing_type": MomentumMixingType.equality,  # booster pump will match pressure
                "inlet_list": ["P1", "P2"],
            }
        )
        desal.PXR = PressureExchanger(default={"property_package": m.fs.prop_desal})
        desal.P2 = Pump(default={"property_package": m.fs.prop_desal})
    elif erd_type == "pump_as_turbine":
        desal.ERD = EnergyRecoveryDevice(default={"property_package": m.fs.prop_desal})
    else:
        raise ConfigurationError(
            "erd_type was {}, but can only "
            "be pressure_exchanger or pump_as_turbine"
            "".format(erd_type)
        )

    # posttreatment
    psttrt.storage_tank_2 = StorageTankZO(
        default={"property_package": m.fs.prop_psttrt, "database": m.db}
    )
    psttrt.uv_aop = UVAOPZO(
        default={
            "property_package": m.fs.prop_psttrt,
            "database": m.db,
            "process_subtype": "hydrogen_peroxide",
        }
    )
    psttrt.co2_addition = CO2AdditionZO(
        default={"property_package": m.fs.prop_psttrt, "database": m.db}
    )
    psttrt.lime_addition = ChemicalAdditionZO(
        default={
            "property_package": m.fs.prop_psttrt,
            "database": m.db,
            "process_subtype": "lime",
        }
    )
    psttrt.storage_tank_3 = StorageTankZO(
        default={"property_package": m.fs.prop_psttrt, "database": m.db}
    )

    # product and disposal
    m.fs.municipal = MunicipalDrinkingZO(
        default={"property_package": m.fs.prop_psttrt, "database": m.db}
    )
    m.fs.landfill = LandfillZO(
        default={"property_package": m.fs.prop_prtrt, "database": m.db}
    )
    m.fs.disposal = Product(default={"property_package": m.fs.prop_desal})

    # translator blocks
    m.fs.tb_prtrt_desal = Translator(
        default={
            "inlet_property_package": m.fs.prop_prtrt,
            "outlet_property_package": m.fs.prop_desal,
        }
    )

    @m.fs.tb_prtrt_desal.Constraint(["H2O", "tds"])
    def eq_flow_mass_comp(blk, j):
        if j == "tds":
            jj = "TDS"
        else:
            jj = j
        return (
            blk.properties_in[0].flow_mass_comp[j]
            == blk.properties_out[0].flow_mass_phase_comp["Liq", jj]
        )

    m.fs.tb_desal_psttrt = Translator(
        default={
            "inlet_property_package": m.fs.prop_desal,
            "outlet_property_package": m.fs.prop_psttrt,
        }
    )

    @m.fs.tb_desal_psttrt.Constraint(["H2O", "TDS"])
    def eq_flow_mass_comp(blk, j):
        if j == "TDS":
            jj = "tds"
        else:
            jj = j
        return (
            blk.properties_in[0].flow_mass_phase_comp["Liq", j]
            == blk.properties_out[0].flow_mass_comp[jj]
        )

    # connections
    m.fs.s_feed = Arc(source=m.fs.feed.outlet, destination=prtrt.intake.inlet)
    prtrt.s01 = Arc(
        source=prtrt.intake.outlet, destination=prtrt.ferric_chloride_addition.inlet
    )
    prtrt.s02 = Arc(
        source=prtrt.ferric_chloride_addition.outlet,
        destination=prtrt.chlorination.inlet,
    )
    prtrt.s03 = Arc(
        source=prtrt.chlorination.treated, destination=prtrt.static_mixer.inlet
    )
    prtrt.s04 = Arc(
        source=prtrt.static_mixer.outlet, destination=prtrt.storage_tank_1.inlet
    )
    prtrt.s05 = Arc(
        source=prtrt.storage_tank_1.outlet, destination=prtrt.media_filtration.inlet
    )
    prtrt.s06 = Arc(
        source=prtrt.media_filtration.byproduct,
        destination=prtrt.backwash_handling.inlet,
    )
    prtrt.s07 = Arc(
        source=prtrt.media_filtration.treated,
        destination=prtrt.anti_scalant_addition.inlet,
    )
    prtrt.s08 = Arc(
        source=prtrt.anti_scalant_addition.outlet,
        destination=prtrt.cartridge_filtration.inlet,
    )
    m.fs.s_prtrt_tb = Arc(
        source=prtrt.cartridge_filtration.treated, destination=m.fs.tb_prtrt_desal.inlet
    )
    m.fs.s_landfill = Arc(
        source=prtrt.backwash_handling.byproduct, destination=m.fs.landfill.inlet
    )

    if erd_type == "pressure_exchanger":
        m.fs.s_tb_desal = Arc(
            source=m.fs.tb_prtrt_desal.outlet, destination=desal.S1.inlet
        )
        desal.s01 = Arc(source=desal.S1.P1, destination=desal.P1.inlet)
        desal.s02 = Arc(source=desal.P1.outlet, destination=desal.M1.P1)
        desal.s03 = Arc(source=desal.M1.outlet, destination=desal.RO.inlet)
        desal.s04 = Arc(
            source=desal.RO.retentate, destination=desal.PXR.high_pressure_inlet
        )
        desal.s05 = Arc(source=desal.S1.PXR, destination=desal.PXR.low_pressure_inlet)
        desal.s06 = Arc(
            source=desal.PXR.low_pressure_outlet, destination=desal.P2.inlet
        )
        desal.s07 = Arc(source=desal.P2.outlet, destination=desal.M1.P2)
        m.fs.s_disposal = Arc(
            source=desal.PXR.high_pressure_outlet, destination=m.fs.disposal.inlet
        )
    elif erd_type == "pump_as_turbine":
        m.fs.s_tb_desal = Arc(
            source=m.fs.tb_prtrt_desal.outlet, destination=desal.P1.inlet
        )
        desal.s01 = Arc(source=desal.P1.outlet, destination=desal.RO.inlet)
        desal.s02 = Arc(source=desal.RO.retentate, destination=desal.ERD.inlet)
        m.fs.s_disposal = Arc(source=desal.ERD.outlet, destination=m.fs.disposal.inlet)
    m.fs.s_desal_tb = Arc(
        source=desal.RO.permeate, destination=m.fs.tb_desal_psttrt.inlet
    )

    m.fs.s_tb_psttrt = Arc(
        source=m.fs.tb_desal_psttrt.outlet, destination=psttrt.storage_tank_2.inlet
    )
    psttrt.s01 = Arc(
        source=psttrt.storage_tank_2.outlet, destination=psttrt.uv_aop.inlet
    )
    psttrt.s02 = Arc(
        source=psttrt.uv_aop.treated, destination=psttrt.co2_addition.inlet
    )
    psttrt.s03 = Arc(
        source=psttrt.co2_addition.outlet, destination=psttrt.lime_addition.inlet
    )
    psttrt.s04 = Arc(
        source=psttrt.lime_addition.outlet, destination=psttrt.storage_tank_3.inlet
    )
    m.fs.s_municipal = Arc(
        source=psttrt.storage_tank_3.outlet, destination=m.fs.municipal.inlet
    )
    TransformationFactory("network.expand_arcs").apply_to(m)

    # scaling
    # set default property values
    m.fs.prop_desal.set_default_scaling(
        "flow_mass_phase_comp", 1e-3, index=("Liq", "H2O")
    )
    m.fs.prop_desal.set_default_scaling(
        "flow_mass_phase_comp", 1e-1, index=("Liq", "TDS")
    )
    # set unit model values
    iscale.set_scaling_factor(desal.P1.control_volume.work, 1e-5)
    iscale.set_scaling_factor(desal.RO.area, 1e-4)
    if erd_type == "pressure_exchanger":
        iscale.set_scaling_factor(desal.P2.control_volume.work, 1e-5)
        iscale.set_scaling_factor(desal.PXR.low_pressure_side.work, 1e-5)
        iscale.set_scaling_factor(desal.PXR.high_pressure_side.work, 1e-5)
    elif erd_type == "pump_as_turbine":
        iscale.set_scaling_factor(desal.ERD.control_volume.work, 1e-5)
    # calculate and propagate scaling factors
    iscale.calculate_scaling_factors(m)

    return m
Esempio n. 22
0
    def test_T350_P1_x5(self):
        m = ConcreteModel()

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

        m.fs.props = BT_PR.BTParameterBlock(
                default={'valid_phase': ('Vap', 'Liq')})

        m.fs.state = m.fs.props.build_state_block(
                default={'defined_state': True})

        m.fs.state.flow_mol.fix(100)
        m.fs.state.mole_frac_comp["benzene"].fix(0.5)
        m.fs.state.mole_frac_comp["toluene"].fix(0.5)
        m.fs.state.temperature.fix(350)
        m.fs.state.pressure.fix(1e5)

        # Trigger build of enthalpy and entropy
        m.fs.state.enth_mol_phase
        m.fs.state.entr_mol_phase

        m.fs.state.initialize()

        solver.solve(m)

        assert pytest.approx(value(m.fs.state._teq), abs=1e-1) == 365
        assert 0.0035346 == pytest.approx(
                value(m.fs.state.compress_fact_phase["Liq"]), 1e-5)
        assert 0.966749 == pytest.approx(
                value(m.fs.state.compress_fact_phase["Vap"]), 1e-5)
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "benzene"]),
                1e-5) == 0.894676
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "toluene"]),
                1e-5) == 0.347566
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "benzene"]),
                1e-5) == 0.971072
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "toluene"]),
                1e-5) == 0.959791

        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "benzene"]),
                1e-5) == 0.5
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "toluene"]),
                1e-5) == 0.5
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "benzene"]),
                1e-5) == 0.70584
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "toluene"]),
                1e-5) == 0.29416

        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Liq"]), 1e-5) == 38942.8
        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Vap"]), 1e-5) == 78048.7
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Liq"]), 1e-5) == -367.558
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Vap"]), 1e-5) == -269.0553
Esempio n. 23
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
Esempio n. 24
0
    def test_T350_P5_x5(self):
        m = ConcreteModel()

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

        m.fs.props = BT_PR.BTParameterBlock(
                default={'valid_phase': ('Vap', 'Liq')})

        m.fs.state = m.fs.props.build_state_block(
                default={'defined_state': True})

        m.fs.state.flow_mol.fix(100)
        m.fs.state.mole_frac_comp["benzene"].fix(0.5)
        m.fs.state.mole_frac_comp["toluene"].fix(0.5)
        m.fs.state.temperature.fix(350)
        m.fs.state.pressure.fix(5e5)

        # Trigger build of enthalpy and entropy
        m.fs.state.enth_mol_phase
        m.fs.state.entr_mol_phase

        m.fs.state.initialize()

        solver.solve(m)

        assert pytest.approx(value(m.fs.state._teq), 1e-5) == 431.47
        assert pytest.approx(
                value(m.fs.state.compress_fact_phase["Liq"]), 1e-5) == 0.01766
        assert pytest.approx(
                value(m.fs.state.compress_fact_phase["Vap"]), 1e-5) == 0.80245
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "benzene"]),
                1e-5) == 0.181229
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "toluene"]),
                1e-5) == 0.070601
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "benzene"]),
                1e-5) == 0.856523
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "toluene"]),
                1e-5) == 0.799237

        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "benzene"]),
                1e-5) == 0.5
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "toluene"]),
                1e-5) == 0.5
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "benzene"]),
                1e-5) == 0.65415
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "toluene"]),
                1e-5) == 0.34585

        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Liq"]), 1e-5) == 38966.9
        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Vap"]), 1e-5) == 75150.7
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Liq"]), 1e-5) == -367.6064
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Vap"]), 1e-5) == -287.3318
def create_model():
    """Create the flowsheet and add unit models. Fixing model inputs is done
    in a separate function to try to keep this fairly clean and easy to follow.

    Args:
        None

    Returns:
        (ConcreteModel) Steam cycle model
    """
    ############################################################################
    #  Flowsheet and Properties                                                #
    ############################################################################
    m = pyo.ConcreteModel(name="Steam Cycle Model")
    m.fs = FlowsheetBlock(default={"dynamic":
                                   False})  # Add steady state flowsheet

    # A physical property parameter block for IAPWS-95 with pressure and enthalpy
    # (PH) state variables.  Usually pressure and enthalpy state variables are
    # more robust especially when the phases are unknown.
    m.fs.prop_water = iapws95.Iapws95ParameterBlock(
        default={"phase_presentation": iapws95.PhaseType.LG})

    # A physical property parameter block with temperature, pressure and vapor
    # fraction (TPx) state variables. There are a few instances where the vapor
    # fraction is known and the temperature and pressure state variables are
    # preferable.
    m.fs.prop_water_tpx = iapws95.Iapws95ParameterBlock(
        default={
            "phase_presentation": iapws95.PhaseType.LG,
            "state_vars": iapws95.StateVars.TPX,
        })
    ############################################################################
    #  Turbine with fill-in reheat constraints                                 #
    ############################################################################
    # The TurbineMultistage class allows creation of the full turbine model by
    # providing several configuration options, including: throttle valves;
    # high-, intermediate-, and low-pressure sections; steam extractions; and
    # pressure driven flow.  See the IDAES documentation for details.
    m.fs.turb = TurbineMultistage(
        default={
            "property_package": m.fs.prop_water,
            "num_parallel_inlet_stages": 4,  # number of admission arcs
            "num_hp": 7,  # number of high-pressure stages
            "num_ip": 10,  # number of intermediate-pressure stages
            "num_lp": 11,  # number of low-pressure stages
            "hp_split_locations": [4, 7],  # hp steam extraction locations
            "ip_split_locations": [5, 10],  # ip steam extraction locations
            "lp_split_locations": [4, 8, 10, 11
                                   ],  # lp steam extraction locations
            "hp_disconnect": [7],  # disconnect hp from ip to insert reheater
            "ip_split_num_outlets": {
                10: 3
            },  # number of split streams (default is 2)
        })
    # This model is only the steam cycle, and the reheater is part of the boiler.
    # To fill in the reheater gap, a few constraints for the flow, pressure drop,
    # and outlet temperature are added. A detailed boiler model can be coupled later.
    #
    # hp_split[7] is the splitter directly after the last HP stage.  The splitter
    # outlet "outlet_1" is always taken to be the main steam flow through the turbine.
    # When the turbine model was instantiated the stream from the HP section to the IP
    # section was omitted, so the reheater could be inserted.

    # The flow constraint sets flow from outlet_1 of the splitter equal to
    # flow into the IP turbine.
    @m.fs.turb.Constraint(m.fs.time)
    def constraint_reheat_flow(b, t):
        return b.ip_stages[1].inlet.flow_mol[t] == b.hp_split[
            7].outlet_1.flow_mol[t]

    # Create a variable for pressure change in the reheater (assuming
    # reheat_delta_p should be negative).
    m.fs.turb.reheat_delta_p = pyo.Var(m.fs.time, initialize=0)

    # Add a constraint to calculate the IP section inlet pressure based on the
    # pressure drop in the reheater and the outlet pressure of the HP section.
    @m.fs.turb.Constraint(m.fs.time)
    def constraint_reheat_press(b, t):
        return (b.ip_stages[1].inlet.pressure[t] ==
                b.hp_split[7].outlet_1.pressure[t] + b.reheat_delta_p[t])

    # Create a variable for reheat temperature and fix it to the desired reheater
    # outlet temperature
    m.fs.turb.reheat_out_T = pyo.Var(m.fs.time, initialize=866)

    # Create a constraint for the IP section inlet temperature.
    @m.fs.turb.Constraint(m.fs.time)
    def constraint_reheat_temp(b, t):
        return (b.ip_stages[1].control_volume.properties_in[t].temperature ==
                b.reheat_out_T[t])

    ############################################################################
    #  Add Condenser/hotwell/condensate pump                                   #
    ############################################################################
    # Add a mixer for all the streams coming into the condenser.  In this case the
    # main steam, and the boiler feed pump turbine outlet go to the condenser
    m.fs.condenser_mix = Mixer(
        default={
            "momentum_mixing_type": MomentumMixingType.none,
            "inlet_list": ["main", "bfpt"],
            "property_package": m.fs.prop_water,
        })
    # The pressure in the mixer comes from the connection to the condenser.  All
    # the streams coming in and going out of the mixer are equal, but we created
    # the mixer with no calculation for the unit pressure. Here a constraint that
    # specifies that the mixer pressure is equal to the main steam pressure is
    # added.  There is also a constraint that specifies the that BFP turbine outlet
    # pressure is the same as the condenser pressure.  Combined with the stream
    # connections between units, these constraints effectively specify that the
    # mixer inlet and outlet streams all have the same pressure.
    @m.fs.condenser_mix.Constraint(m.fs.time)
    def mixer_pressure_constraint(b, t):
        return b.main_state[t].pressure == b.mixed_state[t].pressure

    # The condenser model uses the physical property model with TPx state
    # variables, while the rest of the model uses PH state variables. To
    # translate between the two property calculations, an extra port is added to
    # the mixer which contains temperature, pressure, and vapor fraction
    # quantities. The first step is to add references to the temperature and
    # vapor fraction expressions in the IAPWS-95 property block. The references
    # are used to handle time indexing in the ports by using the property blocks
    # time index to create references that appear to be time indexed variables.
    # These references mirror the references created by the framework automatically
    # for the existing ports.
    m.fs.condenser_mix._outlet_temperature_ref = pyo.Reference(
        m.fs.condenser_mix.mixed_state[:].temperature)
    m.fs.condenser_mix._outlet_vapor_fraction_ref = pyo.Reference(
        m.fs.condenser_mix.mixed_state[:].vapor_frac)
    # Add the new port with the state information that needs to go to the
    # condenser
    m.fs.condenser_mix.outlet_tpx = Port(
        initialize={
            "flow_mol": m.fs.condenser_mix._outlet_flow_mol_ref,
            "temperature": m.fs.condenser_mix._outlet_temperature_ref,
            "pressure": m.fs.condenser_mix._outlet_pressure_ref,
            "vapor_frac": m.fs.condenser_mix._outlet_vapor_fraction_ref,
        })

    # Add the heat exchanger model for the condenser.
    m.fs.condenser = HeatExchanger(
        default={
            "delta_temperature_callback": delta_temperature_underwood_callback,
            "shell": {
                "property_package": m.fs.prop_water_tpx
            },
            "tube": {
                "property_package": m.fs.prop_water
            },
        })
    m.fs.condenser.delta_temperature_out.fix(5)

    # Everything condenses so the saturation pressure determines the condenser
    # pressure. Deactivate the constraint that is used in the TPx version vapor
    # fraction constraint and fix vapor fraction to 0.
    m.fs.condenser.shell.properties_out[:].eq_complementarity.deactivate()
    m.fs.condenser.shell.properties_out[:].vapor_frac.fix(0)

    # There is some subcooling in the condenser, so we assume the condenser
    # pressure is actually going to be slightly higher than the saturation
    # pressure.
    m.fs.condenser.pressure_over_sat = pyo.Var(
        m.fs.time,
        initialize=500,
        doc="Pressure added to Psat in the condeser. This is to account for"
        "some subcooling. (Pa)",
    )
    # Add a constraint for condenser pressure
    @m.fs.condenser.Constraint(m.fs.time)
    def eq_pressure(b, t):
        return (b.shell.properties_out[t].pressure ==
                b.shell.properties_out[t].pressure_sat +
                b.pressure_over_sat[t])

    # Extra port on condenser to hook back up to pressure-enthalpy properties
    m.fs.condenser._outlet_1_enth_mol_ref = pyo.Reference(
        m.fs.condenser.shell.properties_out[:].enth_mol)
    m.fs.condenser.outlet_1_ph = Port(
        initialize={
            "flow_mol": m.fs.condenser._outlet_1_flow_mol_ref,
            "pressure": m.fs.condenser._outlet_1_pressure_ref,
            "enth_mol": m.fs.condenser._outlet_1_enth_mol_ref,
        })

    # Add the condenser hotwell.  In steady state a mixer will work.  This is
    # where makeup water is added if needed.
    m.fs.hotwell = Mixer(
        default={
            "momentum_mixing_type": MomentumMixingType.none,
            "inlet_list": ["condensate", "makeup"],
            "property_package": m.fs.prop_water,
        })

    # The hotwell is assumed to be at the same pressure as the condenser.
    @m.fs.hotwell.Constraint(m.fs.time)
    def mixer_pressure_constraint(b, t):
        return b.condensate_state[t].pressure == b.mixed_state[t].pressure

    # Condensate pump
    m.fs.cond_pump = PressureChanger(
        default={
            "property_package": m.fs.prop_water,
            "thermodynamic_assumption": ThermodynamicAssumption.pump,
        })
    ############################################################################
    #  Add low pressure feedwater heaters                                      #
    ############################################################################
    # All the feedwater heater sections will be set to use the Underwood
    # approximation for LMTD, so create the fwh_config dict to make the config
    # slightly cleaner
    fwh_config = {
        "delta_temperature_callback": delta_temperature_underwood_callback
    }

    # The feedwater heater model allows feedwater heaters with a desuperheat,
    # condensing, and subcooling section to be added an a reasonably simple way.
    # See the IDAES documentation for more information of configuring feedwater
    # heaters
    m.fs.fwh1 = FWH0D(
        default={
            "has_desuperheat": False,
            "has_drain_cooling": False,
            "has_drain_mixer": True,
            "property_package": m.fs.prop_water,
            "condense": fwh_config,
        })
    # pump for fwh1 condensate, to pump it ahead and mix with feedwater
    m.fs.fwh1_pump = PressureChanger(
        default={
            "property_package": m.fs.prop_water,
            "thermodynamic_assumption": ThermodynamicAssumption.pump,
        })
    # Mix the FWH1 drain back into the feedwater
    m.fs.fwh1_return = Mixer(
        default={
            "momentum_mixing_type": MomentumMixingType.none,
            "inlet_list": ["feedwater", "fwh1_drain"],
            "property_package": m.fs.prop_water,
        })

    # Set the mixer pressure to the feedwater pressure
    @m.fs.fwh1_return.Constraint(m.fs.time)
    def mixer_pressure_constraint(b, t):
        return b.feedwater_state[t].pressure == b.mixed_state[t].pressure

    # Add the rest of the low pressure feedwater heaters
    m.fs.fwh2 = FWH0D(
        default={
            "has_desuperheat": True,
            "has_drain_cooling": True,
            "has_drain_mixer": True,
            "property_package": m.fs.prop_water,
            "desuperheat": fwh_config,
            "cooling": fwh_config,
            "condense": fwh_config,
        })
    m.fs.fwh3 = FWH0D(
        default={
            "has_desuperheat": True,
            "has_drain_cooling": True,
            "has_drain_mixer": True,
            "property_package": m.fs.prop_water,
            "desuperheat": fwh_config,
            "cooling": fwh_config,
            "condense": fwh_config,
        })
    m.fs.fwh4 = FWH0D(
        default={
            "has_desuperheat": True,
            "has_drain_cooling": True,
            "has_drain_mixer": False,
            "property_package": m.fs.prop_water,
            "desuperheat": fwh_config,
            "cooling": fwh_config,
            "condense": fwh_config,
        })
    ############################################################################
    #  Add deaerator and boiler feed pump (BFP)                                #
    ############################################################################
    # The deaerator is basically an open tank with multiple inlets.  For steady-
    # state, a mixer model is sufficient.
    m.fs.fwh5_da = Mixer(
        default={
            "momentum_mixing_type": MomentumMixingType.none,
            "inlet_list": ["steam", "drain", "feedwater"],
            "property_package": m.fs.prop_water,
        })

    @m.fs.fwh5_da.Constraint(m.fs.time)
    def mixer_pressure_constraint(b, t):
        # Not sure about deaerator pressure, so assume same as feedwater inlet
        return b.feedwater_state[t].pressure == b.mixed_state[t].pressure

    # Add the boiler feed pump and boiler feed pump turbine
    m.fs.bfp = PressureChanger(
        default={
            "property_package": m.fs.prop_water,
            "thermodynamic_assumption": ThermodynamicAssumption.pump,
        })
    m.fs.bfpt = PressureChanger(
        default={
            "property_package": m.fs.prop_water,
            "compressor": False,
            "thermodynamic_assumption": ThermodynamicAssumption.isentropic,
        })

    # The boiler feed pump outlet pressure is the same as the condenser
    @m.fs.Constraint(m.fs.time)
    def constraint_out_pressure(b, t):
        return (b.bfpt.control_volume.properties_out[t].pressure ==
                b.condenser.shell.properties_out[t].pressure)

    # Instead of specifying a fixed efficiency, specify that the steam is just
    # starting to condense at the outlet of the boiler feed pump turbine.  This
    # ensures approximately the right behavior in the turbine.  With a fixed
    # efficiency, depending on the conditions you can get odd things like steam
    # fully condensing in the turbine.
    @m.fs.Constraint(m.fs.time)
    def constraint_out_enthalpy(b, t):
        return (
            b.bfpt.control_volume.properties_out[t].enth_mol ==
            b.bfpt.control_volume.properties_out[t].enth_mol_sat_phase["Vap"] -
            200)

    # The boiler feed pump power is the same as the power generated by the
    # boiler feed pump turbine. This constraint determines the steam flow to the
    # BFP turbine. The turbine work is negative for power out, while pump work
    # is positive for power in.
    @m.fs.Constraint(m.fs.time)
    def constraint_bfp_power(b, t):
        return 0 == b.bfp.control_volume.work[t] + b.bfpt.control_volume.work[t]

    ############################################################################
    #  Add high pressure feedwater heaters                                     #
    ############################################################################
    m.fs.fwh6 = FWH0D(
        default={
            "has_desuperheat": True,
            "has_drain_cooling": True,
            "has_drain_mixer": True,
            "property_package": m.fs.prop_water,
            "desuperheat": fwh_config,
            "cooling": fwh_config,
            "condense": fwh_config,
        })
    m.fs.fwh7 = FWH0D(
        default={
            "has_desuperheat": True,
            "has_drain_cooling": True,
            "has_drain_mixer": True,
            "property_package": m.fs.prop_water,
            "desuperheat": fwh_config,
            "cooling": fwh_config,
            "condense": fwh_config,
        })
    m.fs.fwh8 = FWH0D(
        default={
            "has_desuperheat": True,
            "has_drain_cooling": True,
            "has_drain_mixer": False,
            "property_package": m.fs.prop_water,
            "desuperheat": fwh_config,
            "cooling": fwh_config,
            "condense": fwh_config,
        })
    ############################################################################
    #  Additional Constraints/Expressions                                      #
    ############################################################################

    # Add a few constraints to allow a for complete plant results despite the
    # lack of a detailed boiler model.

    # Boiler pressure drop
    m.fs.boiler_pressure_drop_fraction = pyo.Var(
        m.fs.time,
        initialize=0.01,
        doc="Fraction of pressure lost from boiler feed pump and turbine inlet",
    )

    @m.fs.Constraint(m.fs.time)
    def boiler_pressure_drop(b, t):
        return (m.fs.bfp.control_volume.properties_out[t].pressure *
                (1 - b.boiler_pressure_drop_fraction[t]) ==
                m.fs.turb.inlet_split.mixed_state[t].pressure)

    # Again, since the boiler is missing, set the flow of steam into the turbine
    # equal to the flow of feedwater out of the last feedwater heater.
    @m.fs.Constraint(m.fs.time)
    def close_flow(b, t):
        return (m.fs.bfp.control_volume.properties_out[t].flow_mol ==
                m.fs.turb.inlet_split.mixed_state[t].flow_mol)

    # Calculate the amount of heat that is added in the boiler, including the
    # reheater.
    @m.fs.Expression(m.fs.time)
    def boiler_heat(b, t):
        return (b.turb.inlet_split.mixed_state[t].enth_mol *
                b.turb.inlet_split.mixed_state[t].flow_mol -
                b.fwh8.desuperheat.tube.properties_out[t].enth_mol *
                b.fwh8.desuperheat.tube.properties_out[t].flow_mol +
                b.turb.ip_stages[1].control_volume.properties_in[t].enth_mol *
                b.turb.ip_stages[1].control_volume.properties_in[t].flow_mol -
                b.turb.hp_split[7].outlet_1.enth_mol[t] *
                b.turb.hp_split[7].outlet_1.flow_mol[t])

    # Calculate the efficiency of the steam cycle.  This doesn't account for
    # heat loss in the boiler, so actual plant efficiency would be lower.
    @m.fs.Expression(m.fs.time)
    def steam_cycle_eff(b, t):
        return -100 * b.turb.power[t] / b.boiler_heat[t]

    ############################################################################
    ##  Create the stream Arcs                                                ##
    ############################################################################

    ############################################################################
    #  Connect turbine and condenser units                                     #
    ############################################################################
    m.fs.EXHST_MAIN = Arc(source=m.fs.turb.outlet_stage.outlet,
                          destination=m.fs.condenser_mix.main)
    m.fs.condenser_mix_to_condenser = Arc(source=m.fs.condenser_mix.outlet_tpx,
                                          destination=m.fs.condenser.inlet_1)
    m.fs.COND_01 = Arc(source=m.fs.condenser.outlet_1_ph,
                       destination=m.fs.hotwell.condensate)
    m.fs.COND_02 = Arc(source=m.fs.hotwell.outlet,
                       destination=m.fs.cond_pump.inlet)
    ############################################################################
    #  Low pressure FWHs                                                       #
    ############################################################################
    m.fs.EXTR_LP11 = Arc(source=m.fs.turb.lp_split[11].outlet_2,
                         destination=m.fs.fwh1.drain_mix.steam)
    m.fs.COND_03 = Arc(source=m.fs.cond_pump.outlet,
                       destination=m.fs.fwh1.condense.inlet_2)
    m.fs.FWH1_DRN1 = Arc(source=m.fs.fwh1.condense.outlet_1,
                         destination=m.fs.fwh1_pump.inlet)
    m.fs.FWH1_DRN2 = Arc(source=m.fs.fwh1_pump.outlet,
                         destination=m.fs.fwh1_return.fwh1_drain)
    m.fs.FW01A = Arc(source=m.fs.fwh1.condense.outlet_2,
                     destination=m.fs.fwh1_return.feedwater)
    # fwh2
    m.fs.FW01B = Arc(source=m.fs.fwh1_return.outlet,
                     destination=m.fs.fwh2.cooling.inlet_2)
    m.fs.FWH2_DRN = Arc(source=m.fs.fwh2.cooling.outlet_1,
                        destination=m.fs.fwh1.drain_mix.drain)
    m.fs.EXTR_LP10 = Arc(
        source=m.fs.turb.lp_split[10].outlet_2,
        destination=m.fs.fwh2.desuperheat.inlet_1,
    )
    # fwh3
    m.fs.FW02 = Arc(source=m.fs.fwh2.desuperheat.outlet_2,
                    destination=m.fs.fwh3.cooling.inlet_2)
    m.fs.FWH3_DRN = Arc(source=m.fs.fwh3.cooling.outlet_1,
                        destination=m.fs.fwh2.drain_mix.drain)
    m.fs.EXTR_LP8 = Arc(source=m.fs.turb.lp_split[8].outlet_2,
                        destination=m.fs.fwh3.desuperheat.inlet_1)
    # fwh4
    m.fs.FW03 = Arc(source=m.fs.fwh3.desuperheat.outlet_2,
                    destination=m.fs.fwh4.cooling.inlet_2)
    m.fs.FWH4_DRN = Arc(source=m.fs.fwh4.cooling.outlet_1,
                        destination=m.fs.fwh3.drain_mix.drain)
    m.fs.EXTR_LP4 = Arc(source=m.fs.turb.lp_split[4].outlet_2,
                        destination=m.fs.fwh4.desuperheat.inlet_1)
    ############################################################################
    #  FWH5 (Deaerator) and boiler feed pump (BFP)                             #
    ############################################################################
    m.fs.FW04 = Arc(source=m.fs.fwh4.desuperheat.outlet_2,
                    destination=m.fs.fwh5_da.feedwater)
    m.fs.EXTR_IP10 = Arc(source=m.fs.turb.ip_split[10].outlet_2,
                         destination=m.fs.fwh5_da.steam)
    m.fs.FW05A = Arc(source=m.fs.fwh5_da.outlet, destination=m.fs.bfp.inlet)
    m.fs.EXTR_BFPT_A = Arc(source=m.fs.turb.ip_split[10].outlet_3,
                           destination=m.fs.bfpt.inlet)
    m.fs.EXHST_BFPT = Arc(source=m.fs.bfpt.outlet,
                          destination=m.fs.condenser_mix.bfpt)
    ############################################################################
    #  High-pressure feedwater heaters                                         #
    ############################################################################
    # fwh6
    m.fs.FW05B = Arc(source=m.fs.bfp.outlet,
                     destination=m.fs.fwh6.cooling.inlet_2)
    m.fs.FWH6_DRN = Arc(source=m.fs.fwh6.cooling.outlet_1,
                        destination=m.fs.fwh5_da.drain)
    m.fs.EXTR_IP5 = Arc(source=m.fs.turb.ip_split[5].outlet_2,
                        destination=m.fs.fwh6.desuperheat.inlet_1)
    # fwh7
    m.fs.FW06 = Arc(source=m.fs.fwh6.desuperheat.outlet_2,
                    destination=m.fs.fwh7.cooling.inlet_2)
    m.fs.FWH7_DRN = Arc(source=m.fs.fwh7.cooling.outlet_1,
                        destination=m.fs.fwh6.drain_mix.drain)
    m.fs.EXTR_HP7 = Arc(source=m.fs.turb.hp_split[7].outlet_2,
                        destination=m.fs.fwh7.desuperheat.inlet_1)
    # fwh8
    m.fs.FW07 = Arc(source=m.fs.fwh7.desuperheat.outlet_2,
                    destination=m.fs.fwh8.cooling.inlet_2)
    m.fs.FWH8_DRN = Arc(source=m.fs.fwh8.cooling.outlet_1,
                        destination=m.fs.fwh7.drain_mix.drain)
    m.fs.EXTR_HP4 = Arc(source=m.fs.turb.hp_split[4].outlet_2,
                        destination=m.fs.fwh8.desuperheat.inlet_1)

    ############################################################################
    # Turn the Arcs into constraints and return the model                      #
    ############################################################################
    pyo.TransformationFactory("network.expand_arcs").apply_to(m.fs)
    return m
Esempio n. 26
0
    def test_T450_P1_x5(self):
        m = ConcreteModel()

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

        m.fs.props = BT_PR.BTParameterBlock(
                default={'valid_phase': ('Vap', 'Liq')})

        m.fs.state = m.fs.props.build_state_block(
                default={'defined_state': True})

        m.fs.state.flow_mol.fix(100)
        m.fs.state.mole_frac_comp["benzene"].fix(0.5)
        m.fs.state.mole_frac_comp["toluene"].fix(0.5)
        m.fs.state.temperature.fix(450)
        m.fs.state.pressure.fix(1e5)

        # Trigger build of enthalpy and entropy
        m.fs.state.enth_mol_phase
        m.fs.state.entr_mol_phase

        m.fs.state.initialize()

        solver.solve(m)

        assert pytest.approx(value(m.fs.state._teq), 1e-5) == 371.4
        assert 0.0033583 == pytest.approx(
                value(m.fs.state.compress_fact_phase["Liq"]), 1e-5)
        assert 0.9821368 == pytest.approx(
                value(m.fs.state.compress_fact_phase["Vap"]), 1e-5)
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "benzene"]),
                1e-5) == 8.069323
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "toluene"]),
                1e-5) == 4.304955
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "benzene"]),
                1e-5) == 0.985365
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "toluene"]),
                1e-5) == 0.979457

        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "benzene"]),
                1e-5) == 0.29861
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "toluene"]),
                1e-5) == 0.70139
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "benzene"]),
                1e-5) == 0.5
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "toluene"]),
                1e-5) == 0.5

        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Liq"]), 1e-5) == 49441.2
        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Vap"]), 1e-5) == 84175.1
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Liq"]), 1e-5) == -333.836
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Vap"]), 1e-5) == -247.385
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
Esempio n. 28
0
    def test_T450_P5_x5(self):
        m = ConcreteModel()

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

        m.fs.props = BT_PR.BTParameterBlock(
                default={'valid_phase': ('Vap', 'Liq')})

        m.fs.state = m.fs.props.build_state_block(
                default={'defined_state': True})

        m.fs.state.flow_mol.fix(100)
        m.fs.state.mole_frac_comp["benzene"].fix(0.5)
        m.fs.state.mole_frac_comp["toluene"].fix(0.5)
        m.fs.state.temperature.fix(450)
        m.fs.state.pressure.fix(5e5)

        # Trigger build of enthalpy and entropy
        m.fs.state.enth_mol_phase
        m.fs.state.entr_mol_phase

        m.fs.state.initialize()

        solver.solve(m)

        assert pytest.approx(value(m.fs.state._teq), 1e-5) == 436.93
        assert 0.0166181 == pytest.approx(
                value(m.fs.state.compress_fact_phase["Liq"]), 1e-5)
        assert 0.9053766 == pytest.approx(
                value(m.fs.state.compress_fact_phase["Vap"]), 1e-5)
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "benzene"]),
                1e-5) == 1.63308
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Liq", "toluene"]),
                1e-5) == 0.873213
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "benzene"]),
                1e-5) == 0.927534
        assert pytest.approx(
                value(m.fs.state.fug_coeff_phase_comp["Vap", "toluene"]),
                1e-5) == 0.898324

        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "benzene"]),
                1e-5) == 0.3488737
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Liq", "toluene"]),
                1e-5) == 0.6511263
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "benzene"]),
                1e-5) == 0.5
        assert pytest.approx(
                value(m.fs.state.mole_frac_phase_comp["Vap", "toluene"]),
                1e-5) == 0.5

        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Liq"]), 1e-5) == 51095.2
        assert pytest.approx(
                value(m.fs.state.enth_mol_phase["Vap"]), 1e-5) == 83362.3
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Liq"]), 1e-5) == -331.676
        assert pytest.approx(
                value(m.fs.state.entr_mol_phase["Vap"]), 1e-5) == -261.961
Esempio n. 29
0
def build_turbine():
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.properties = iapws95.Iapws95ParameterBlock()
    m.fs.turb = HelmTurbineOutletStage(default={"property_package": m.fs.properties})
    return m
    def test_Pdrop_fixed_per_unit_length(self):
        """ Testing 0D-RO with PressureChangeType.fixed_per_unit_length option.
        """
        m = ConcreteModel()
        m.fs = FlowsheetBlock(default={"dynamic": False})

        m.fs.properties = props.NaClParameterBlock()

        m.fs.unit = ReverseOsmosis0D(
            default={
                "property_package": m.fs.properties,
                "has_pressure_change": True,
                "concentration_polarization_type":
                ConcentrationPolarizationType.calculated,
                "mass_transfer_coefficient":
                MassTransferCoefficient.calculated,
                "pressure_change_type":
                PressureChangeType.fixed_per_unit_length
            })

        # fully specify system
        feed_flow_mass = 1
        feed_mass_frac_NaCl = 0.035
        feed_mass_frac_H2O = 1 - feed_mass_frac_NaCl
        feed_pressure = 50e5
        feed_temperature = 273.15 + 25
        membrane_area = 50
        length = 20
        A = 4.2e-12
        B = 3.5e-8
        pressure_atmospheric = 101325
        membrane_pressure_drop = 3e5

        m.fs.unit.inlet.flow_mass_phase_comp[0, 'Liq', 'NaCl'].fix(
            feed_flow_mass * feed_mass_frac_NaCl)
        m.fs.unit.inlet.flow_mass_phase_comp[0, 'Liq', 'H2O'].fix(
            feed_flow_mass * feed_mass_frac_H2O)
        m.fs.unit.inlet.pressure[0].fix(feed_pressure)
        m.fs.unit.inlet.temperature[0].fix(feed_temperature)
        m.fs.unit.area.fix(membrane_area)
        m.fs.unit.A_comp.fix(A)
        m.fs.unit.B_comp.fix(B)
        m.fs.unit.permeate.pressure[0].fix(pressure_atmospheric)

        m.fs.unit.channel_height.fix(0.002)
        m.fs.unit.spacer_porosity.fix(0.75)
        m.fs.unit.length.fix(length)
        m.fs.unit.dP_dx.fix(-membrane_pressure_drop / length)

        # test statistics
        assert number_variables(m) == 110
        assert number_total_constraints(m) == 80
        assert number_unused_variables(m) == 0

        # test degrees of freedom
        assert degrees_of_freedom(m) == 0

        # test scaling
        m.fs.properties.set_default_scaling('flow_mass_phase_comp',
                                            1,
                                            index=('Liq', 'H2O'))
        m.fs.properties.set_default_scaling('flow_mass_phase_comp',
                                            1e2,
                                            index=('Liq', 'NaCl'))

        calculate_scaling_factors(m)

        # check that all variables have scaling factors.
        # TODO: see aforementioned TODO on revisiting scaling and associated testing for property models.
        unscaled_var_list = list(
            unscaled_variables_generator(m.fs.unit, include_fixed=True))
        assert len(unscaled_var_list) == 0

        # check that all constraints have been scaled
        unscaled_constraint_list = list(unscaled_constraints_generator(m))
        assert len(unscaled_constraint_list) == 0

        # test initialization
        initialization_tester(m)

        # test variable scaling
        badly_scaled_var_lst = list(badly_scaled_var_generator(m))
        assert badly_scaled_var_lst == []

        # test solve
        solver.options = {'nlp_scaling_method': 'user-scaling'}
        results = solver.solve(m, tee=True)

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

        # test solution
        assert (pytest.approx(-3.000e5,
                              rel=1e-3) == value(m.fs.unit.deltaP[0]))
        assert (pytest.approx(2.205e-3, rel=1e-3) == value(
            m.fs.unit.flux_mass_phase_comp_avg[0, 'Liq', 'H2O']))
        assert (pytest.approx(1.826e-6, rel=1e-3) == value(
            m.fs.unit.flux_mass_phase_comp_avg[0, 'Liq', 'NaCl']))
        assert (pytest.approx(0.1103, rel=1e-3) == value(
            m.fs.unit.properties_permeate[0].flow_mass_phase_comp['Liq',
                                                                  'H2O']))
        assert (pytest.approx(9.129e-5, rel=1e-3) == value(
            m.fs.unit.properties_permeate[0].flow_mass_phase_comp['Liq',
                                                                  'NaCl']))
        assert (pytest.approx(35.751, rel=1e-3) == value(
            m.fs.unit.feed_side.properties_in[0].conc_mass_phase_comp['Liq',
                                                                      'NaCl']))
        assert (pytest.approx(53.506, rel=1e-3) == value(
            m.fs.unit.feed_side.properties_interface_in[0].
            conc_mass_phase_comp['Liq', 'NaCl']))
        assert (pytest.approx(40.207, rel=1e-3) == value(
            m.fs.unit.feed_side.properties_out[0].conc_mass_phase_comp['Liq',
                                                                       'NaCl'])
                )
        assert (pytest.approx(52.476, rel=1e-3) == value(
            m.fs.unit.feed_side.properties_interface_out[0].
            conc_mass_phase_comp['Liq', 'NaCl']))