def build_ideal_naocl_mixer_unit(model):
    model.fs.ideal_naocl_mixer_unit = Mixer(
        default={
            "property_package": model.fs.ideal_naocl_thermo_params,
            "inlet_list": ["inlet_stream", "naocl_stream"],
        })

    # add new constraint for dosing rate (deactivate constraint for OCl_-)
    dr = value(
        model.fs.ideal_naocl_mixer_unit.naocl_stream.flow_mol[0] *
        model.fs.ideal_naocl_mixer_unit.naocl_stream.mole_frac_comp[0, "OCl_-"]
        * model.fs.ideal_naocl_thermo_params.get_component("OCl_-").mw)
    model.fs.ideal_naocl_mixer_unit.dosing_rate = Var(
        initialize=dr,
        domain=NonNegativeReals,
        units=pyunits.kg / pyunits.s,
    )

    def _dosing_rate_cons(blk):
        return blk.dosing_rate == (
            blk.naocl_stream.flow_mol[0] *
            blk.naocl_stream.mole_frac_comp[0, "OCl_-"] *
            blk.naocl_stream_state[0].params.get_component("OCl_-").mw) + (
                blk.naocl_stream.flow_mol[0] *
                blk.naocl_stream.mole_frac_comp[0, "Na_+"] *
                blk.naocl_stream_state[0].params.get_component("Na_+").mw)

    model.fs.ideal_naocl_mixer_unit.dosing_cons = Constraint(
        rule=_dosing_rate_cons)
Esempio n. 2
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. 3
0
def build_flowsheet():
    m = ConcreteModel()

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

    m.fs.BT_props = BTXParameterBlock()

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

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

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

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

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

    return m
Esempio n. 4
0
def concrete_model_base(m):
    """
    Concrete and declare all units and streams in the model.
    Optional keyword arguments:
    ---------------------------
    :param m: the model
    :return: return the model
    """
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.thermo_params = thermo_props.MethaneParameterBlock()
    # Declare all Units
    m.fs.HX1 = Heater(default={"property_package": m.fs.thermo_params})
    m.fs.HX2a = Heater(default={"property_package": m.fs.thermo_params})
    m.fs.HX2b = Heater(default={"property_package": m.fs.thermo_params})
    m.fs.Mix1 = Mixer(default={
        "dynamic": False,
        "property_package": m.fs.thermo_params
    })
    m.fs.Mix2 = Mixer(default={
        "dynamic": False,
        "property_package": m.fs.thermo_params
    })
    m.fs.Mix3 = Mixer(default={
        "dynamic": False,
        "property_package": m.fs.thermo_params
    })
    m.fs.Split1 = Separator(
        default={
            "dynamic": False,
            "split_basis": SplittingType.componentFlow,
            "property_package": m.fs.thermo_params
        })
    m.fs.Reformer = GibbsReactor(
        default={
            "dynamic": False,
            "property_package": m.fs.thermo_params,
            "has_pressure_change": False,
            "has_heat_transfer": True
        })
    m.fs.SOFC = GibbsReactor(
        default={
            "dynamic": False,
            "property_package": m.fs.thermo_params,
            "has_pressure_change": False,
            "has_heat_transfer": True
        })
    m.fs.Burner = GibbsReactor(
        default={
            "dynamic": False,
            "property_package": m.fs.thermo_params,
            "has_pressure_change": False,
            "has_heat_transfer": True
        })
    # Declare all Streams
    m.fs.stream0 = Arc(source=m.fs.Mix1.outlet, destination=m.fs.HX1.inlet)
    m.fs.stream1 = Arc(source=m.fs.Split1.outlet_1,
                       destination=m.fs.HX2b.inlet)
    m.fs.stream2 = Arc(source=m.fs.HX1.outlet, destination=m.fs.Reformer.inlet)
    m.fs.stream3 = Arc(source=m.fs.Split1.outlet_2,
                       destination=m.fs.HX2a.inlet)
    m.fs.stream4 = Arc(source=m.fs.Reformer.outlet,
                       destination=m.fs.Mix2.inlet_1)
    m.fs.stream5 = Arc(source=m.fs.HX2b.outlet, destination=m.fs.Mix2.inlet_2)
    m.fs.stream6 = Arc(source=m.fs.Mix2.outlet, destination=m.fs.SOFC.inlet)
    m.fs.stream7 = Arc(source=m.fs.HX2a.outlet, destination=m.fs.Mix3.inlet_2)
    m.fs.stream8 = Arc(source=m.fs.SOFC.outlet, destination=m.fs.Mix3.inlet_1)
    m.fs.stream9 = Arc(source=m.fs.Mix3.outlet, destination=m.fs.Burner.inlet)
    TransformationFactory("network.expand_arcs").apply_to(m)

    return m
Esempio n. 5
0
    def build(self):
        """
        Begin building model (pre-DAE transformation).
        Args:
            None
        Returns:
            None
        """

        # Call UnitModel.build to setup dynamics
        super().build()

        # Build Control Volume
        self.control_volume = ControlVolume0DBlock(default={
                "dynamic": self.config.dynamic,
                "has_holdup": self.config.has_holdup,
                "property_package": self.config.property_package,
                "property_package_args": self.config.property_package_args})

        self.control_volume.add_geometry()

        self.control_volume.add_state_blocks(has_phase_equilibrium=False)

        self.control_volume.add_material_balances(
            balance_type=self.config.material_balance_type,)

        self.control_volume.add_energy_balances(
            balance_type=self.config.energy_balance_type,
            has_heat_transfer=self.config.has_heat_transfer)

        self.control_volume.add_momentum_balances(
            balance_type=self.config.momentum_balance_type,
            has_pressure_change=True)

        self.flash = HelmPhaseSeparator(
            default={
                "dynamic": False,
                "property_package": self.config.property_package,
            }
        )

        self.mixer = Mixer(
            default={
                "dynamic": False,
                "property_package": self.config.property_package,
                "inlet_list": ["FeedWater", "SaturatedWater"],
                "mixed_state_block": self.control_volume.properties_in,
                }
            )
        # instead of creating a new block use control volume to return solution

        # Inlet Ports
        # FeedWater to Drum (from Pipe or Economizer)
        self.feedwater_inlet = Port(extends=self.mixer.FeedWater)
        # Sat water from water wall
        self.water_steam_inlet = Port(extends=self.flash.inlet)
        # Exit Ports
        # Liquid to Downcomer
        # self.liquid_outlet = Port(extends=self.mixer.outlet)
        self.add_outlet_port('liquid_outlet', self.control_volume)
        # Steam to superheaters
        self.steam_outlet = Port(extends=self.flash.vap_outlet)

        # constraint to make pressures of two inlets of drum mixer the same
        @self.Constraint(self.flowsheet().config.time,
                         doc="Mixter pressure identical")
        def mixer_pressure_eqn(b, t):
            return b.mixer.SaturatedWater.pressure[t]*1e-6 == \
                b.mixer.FeedWater.pressure[t]*1e-6

        self.stream_flash_out = Arc(
            source=self.flash.liq_outlet, destination=self.mixer.SaturatedWater
            )
        # Pyomo arc connect flash liq_outlet with mixer SaturatedWater inlet
        pyo.TransformationFactory("network.expand_arcs").apply_to(self)

        # Add object references
        self.volume = pyo.Reference(self.control_volume.volume)

        # Set references to balance terms at unit level
        if (self.config.has_heat_transfer is True and
                self.config.energy_balance_type != EnergyBalanceType.none):
            self.heat_duty = pyo.Reference(self.control_volume.heat)

        if (self.config.has_pressure_change is True and
                self.config.momentum_balance_type != 'none'):
            self.deltaP = pyo.Reference(self.control_volume.deltaP)

        # Set Unit Geometry and Holdup Volume
        self._set_geometry()

        # Construct performance equations
        self._make_performance()
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 build_boiler(fs):

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

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

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

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

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

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

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

    # Build connections (streams)

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

    # Flue gas route ---------------------------------------------------------
    # water wall connected with boiler block (to fix the heat duty)
    # platen SH connected with boiler block (to fix the heat duty)
    # Finishing superheater connected with a flowsheet level constraint
    fs.fg_fsh2_separator = Arc(source=fs.FSH.side_2_outlet,
                               destination=fs.Spl1.inlet)
    fs.fg_fsh2rh = Arc(source=fs.Spl1.outlet_1, destination=fs.RH.side_2_inlet)
    fs.fg_fsh2PrSH = Arc(source=fs.Spl1.outlet_2,
                         destination=fs.PrSH.side_2_inlet)
    fs.fg_rhtomix = Arc(source=fs.RH.side_2_outlet,
                        destination=fs.mix1.Reheat_out)
    fs.fg_prsh2mix = Arc(source=fs.PrSH.side_2_outlet,
                         destination=fs.mix1.PrSH_out)
    fs.fg_mix2econ = Arc(source=fs.mix1.outlet,
                         destination=fs.ECON.side_2_inlet)
Esempio n. 8
0
class FWH0DData(UnitModelBlockData):
    CONFIG = UnitModelBlockData.CONFIG()
    _define_feedwater_heater_0D_config(CONFIG)

    def build(self):
        super().build()
        config = self.config  # sorter ref to config for less line splitting

        # All feedwater heaters have a condensing section
        _set_prop_pack(config.condense, config)
        self.condense = FWHCondensing0D(default=config.condense)

        # Add a mixer to add the drain stream from another feedwater heater
        if config.has_drain_mixer:
            mix_cfg = {  # general unit model config
                "dynamic": config.dynamic,
                "has_holdup": config.has_holdup,
                "property_package": config.property_package,
                "property_package_args": config.property_package_args,
                "momentum_mixing_type": MomentumMixingType.none,
                "material_balance_type": MaterialBalanceType.componentTotal,
                "inlet_list": ["steam", "drain"],
            }
            self.drain_mix = Mixer(default=mix_cfg)

            @self.drain_mix.Constraint(self.drain_mix.flowsheet().config.time)
            def mixer_pressure_constraint(b, t):
                """
                Constraint to set the drain mixer pressure to the pressure of
                the steam extracted from the turbine. The drain inlet should
                always be a higher pressure than the steam inlet.
                """
                return b.steam_state[t].pressure == b.mixed_state[t].pressure

            # Connect the mixer to the condensing section inlet
            self.mix_out_arc = Arc(source=self.drain_mix.outlet,
                                   destination=self.condense.inlet_1)

        # Add a desuperheat section before the condensing section
        if config.has_desuperheat:
            _set_prop_pack(config.desuperheat, config)
            self.desuperheat = HeatExchanger(default=config.desuperheat)
            # set default area less than condensing section area, this will
            # almost always be overridden by the user fixing an area later
            self.desuperheat.area.value = 10
            if config.has_drain_mixer:
                self.desuperheat_drain_arc = Arc(
                    source=self.desuperheat.outlet_1,
                    destination=self.drain_mix.steam)
            else:
                self.desuperheat_drain_arc = Arc(
                    source=self.desuperheat.outlet_1,
                    destination=self.condense.inlet_1)
            self.condense_out2_arc = Arc(source=self.condense.outlet_2,
                                         destination=self.desuperheat.inlet_2)

        # Add a drain cooling section after the condensing section
        if config.has_drain_cooling:
            _set_prop_pack(config.cooling, config)
            self.cooling = HeatExchanger(default=config.cooling)
            # set default area less than condensing section area, this will
            # almost always be overridden by the user fixing an area later
            self.cooling.area.value = 10
            self.cooling_out2_arc = Arc(source=self.cooling.outlet_2,
                                        destination=self.condense.inlet_2)
            self.condense_out1_arc = Arc(source=self.condense.outlet_1,
                                         destination=self.cooling.inlet_1)

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

    def initialize(self, *args, **kwargs):
        outlvl = kwargs.get("outlvl", idaeslog.NOTSET)

        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")

        config = self.config  # shorter ref to config for less line splitting
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)

        # the initialization here isn't straight forward since the heat exchanger
        # may have 3 stages and they are countercurrent.  For simplicity each
        # stage in initialized with the same cooling water inlet conditions then
        # the whole feedwater heater is solved together.  There are more robust
        # approaches which can be implimented if the need arises.

        # initialize desuperheat if include
        if config.has_desuperheat:
            if config.has_drain_cooling:
                _set_port(self.desuperheat.inlet_2, self.cooling.inlet_2)
            else:
                _set_port(self.desuperheat.inlet_2, self.condense.inlet_2)
            self.desuperheat.initialize(*args, **kwargs)
            self.desuperheat.inlet_1.flow_mol.unfix()
            if config.has_drain_mixer:
                _set_port(self.drain_mix.steam, self.desuperheat.outlet_1)
            else:
                _set_port(self.condense.inlet_1, self.desuperheat.outlet_1)
            # fix the steam and fwh inlet for init
            self.desuperheat.inlet_1.fix()
            self.desuperheat.inlet_1.flow_mol.unfix()  # unfix for extract calc
        # initialize mixer if included
        if config.has_drain_mixer:
            self.drain_mix.steam.fix()
            self.drain_mix.drain.fix()
            self.drain_mix.outlet.unfix()
            self.drain_mix.initialize(*args, **kwargs)
            _set_port(self.condense.inlet_1, self.drain_mix.outlet)
            if config.has_desuperheat:
                self.drain_mix.steam.unfix()
            else:
                self.drain_mix.steam.flow_mol.unfix()
        # Initialize condense section
        if config.has_drain_cooling:
            _set_port(self.condense.inlet_2, self.cooling.inlet_2)
            self.cooling.inlet_2.fix()
        else:
            self.condense.inlet_2.fix()
        if not config.has_drain_mixer and not config.has_desuperheat:
            self.condense.inlet_1.fix()
            self.condense.inlet_1.flow_mol.unfix()

        tempsat = value(self.condense.shell.properties_in[0].temperature_sat)
        temp = value(self.condense.tube.properties_in[0].temperature)
        if tempsat - temp < 30:
            init_log.warning(
                "The steam sat. temperature ({}) is near the feedwater"
                " inlet temperature ({})".format(tempsat, temp))

        self.condense.initialize(*args, **kwargs)
        # Initialize drain cooling if included
        if config.has_drain_cooling:
            _set_port(self.cooling.inlet_1, self.condense.outlet_1)
            self.cooling.initialize(*args, **kwargs)
        # Solve all together
        opt = SolverFactory(kwargs.get("solver", "ipopt"))
        opt.options = kwargs.get("oparg", {})
        assert degrees_of_freedom(self) == 0
        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = opt.solve(self, tee=slc.tee)
        init_log.info("Condensing shell inlet delta T = {}".format(
            value(self.condense.delta_temperature_in[0])))
        init_log.info("Condensing Shell outlet delta T = {}".format(
            value(self.condense.delta_temperature_out[0])))
        init_log.info("Steam Flow = {}".format(
            value(self.condense.inlet_1.flow_mol[0])))
        init_log.info("Initialization Complete: {}".format(
            idaeslog.condition(res)))

        from_json(self, sd=istate, wts=sp)
Esempio n. 9
0
def make_model(horizon=6,
               ntfe=60,
               ntcp=2,
               inlet_E=11.91,
               inlet_S=12.92,
               steady=False,
               bounds=False):
    time_set = [0, horizon]

    m = ConcreteModel(name='CSTR model for testing')
    if steady:
        m.fs = FlowsheetBlock(default={'dynamic': False})
    else:
        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={
            'has_holdup': True,
            '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
        })

    m.fs.mixer = Mixer(
        default={
            'property_package': m.fs.properties,
            'material_balance_type': MaterialBalanceType.componentTotal,
            'momentum_mixing_type': MomentumMixingType.none,
            'num_inlets': 2,
            'inlet_list': ['S_inlet', 'E_inlet']
        })
    # Allegedly the proper energy balance is being used...

    # Time discretization
    if not steady:
        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:
    if not steady:
        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.001)
    # Note: Model does not solve when initial conditions are empty tank

    m.fs.mixer.E_inlet.conc_mol.fix(0)
    m.fs.mixer.S_inlet.conc_mol.fix(0)

    for t, j in m.fs.time * m.fs.properties.component_list:
        if j == 'E':
            m.fs.mixer.E_inlet.conc_mol[t, j].fix(inlet_E)
        elif j == 'S':
            m.fs.mixer.S_inlet.conc_mol[t, j].fix(inlet_S)

    m.fs.mixer.E_inlet.flow_vol.fix(0.1)
    m.fs.mixer.S_inlet.flow_vol.fix(2.1)
    m.fs.mixer.E_inlet.conc_mol[:, 'Solvent'].fix(1.)
    m.fs.mixer.S_inlet.conc_mol[:, 'Solvent'].fix(1.)

    m.fs.mixer.E_inlet.temperature.fix(290)
    m.fs.mixer.S_inlet.temperature.fix(310)

    m.fs.inlet = Arc(source=m.fs.mixer.outlet, destination=m.fs.cstr.inlet)

    # This constraint is in lieu of tracking the CSTR's level and allowing
    # the outlet flow rate to be another degree of freedom.
    # ^ Not sure how to do this in IDAES.
    @m.fs.cstr.Constraint(m.fs.time, doc='Total flow rate balance')
    def total_flow_balance(cstr, t):
        return (cstr.inlet.flow_vol[t] == cstr.outlet.flow_vol[t])

    # Specify initial condition for energy
    if not steady:
        m.fs.cstr.control_volume.energy_holdup[m.fs.time.first(),
                                               'aq'].fix(300)

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

    if bounds:
        m.fs.mixer.E_inlet.flow_vol.setlb(0.01)
        m.fs.mixer.E_inlet.flow_vol.setub(1.0)
        m.fs.mixer.S_inlet.flow_vol.setlb(0.5)
        m.fs.mixer.S_inlet.flow_vol.setub(5.0)

        m.fs.cstr.control_volume.material_holdup.setlb(0)
        holdup = m.fs.cstr.control_volume.material_holdup
        for t in m.fs.time:
            holdup[t, 'aq', 'S'].setub(20)
            holdup[t, 'aq', 'E'].setub(1)
            holdup[t, 'aq', 'P'].setub(5)
            holdup[t, 'aq', 'C'].setub(5)

    return m
Esempio n. 10
0
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
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.costing.cost_flow(
        pyunits.convert(m.fs.P1.work_mechanical[0], to_units=pyunits.kW),
        "electricity")
    m.fs.costing.cost_flow(
        pyunits.convert(m.fs.P2.work_mechanical[0], to_units=pyunits.kW),
        "electricity")
    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_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. 12
0
    def build(self):
        super(TurbineMultistageData, self).build()
        config = self.config
        unit_cfg = {  # general unit model config
            "dynamic": config.dynamic,
            "has_holdup": config.has_holdup,
            "has_phase_equilibrium": config.has_phase_equilibrium,
            "material_balance_type": config.material_balance_type,
            "property_package": config.property_package,
            "property_package_args": config.property_package_args,
        }
        ni = self.config.num_parallel_inlet_stages
        inlet_idx = self.inlet_stage_idx = RangeSet(ni)

        # Adding unit models
        # ------------------------

        # Splitter to inlet that splits main flow into parallel flows for
        # paritial arc admission to the turbine
        self.inlet_split = Separator(default=self._split_cfg(unit_cfg, ni))
        self.throttle_valve = SteamValve(inlet_idx, default=unit_cfg)
        self.inlet_stage = TurbineInletStage(inlet_idx, default=unit_cfg)
        # mixer to combine the parallel flows back together
        self.inlet_mix = Mixer(default=self._mix_cfg(unit_cfg, ni))
        # add turbine sections.
        # inlet stage -> hp stages -> ip stages -> lp stages -> outlet stage
        self.hp_stages = TurbineStage(
            RangeSet(config.num_hp), default=unit_cfg)
        self.ip_stages = TurbineStage(
            RangeSet(config.num_ip), default=unit_cfg)
        self.lp_stages = TurbineStage(
            RangeSet(config.num_lp), default=unit_cfg)
        self.outlet_stage = TurbineOutletStage(default=unit_cfg)

        for i in self.hp_stages:
            self.hp_stages[i].ratioP.fix()
            self.hp_stages[i].efficiency_isentropic[:].fix()
        for i in self.ip_stages:
            self.ip_stages[i].ratioP.fix()
            self.ip_stages[i].efficiency_isentropic[:].fix()
        for i in self.lp_stages:
            self.lp_stages[i].ratioP.fix()
            self.lp_stages[i].efficiency_isentropic[:].fix()

        # Then make splitter config.  If number of outlets is specified
        # make a specific config, otherwise use default with 2 outlets
        s_sfg_default = self._split_cfg(unit_cfg, 2)
        hp_splt_cfg = {}
        ip_splt_cfg = {}
        lp_splt_cfg = {}
        # Now to finish up if there are more than two outlets, set that
        for i, v in config.hp_split_num_outlets.items():
            hp_splt_cfg[i] = self._split_cfg(unit_cfg, v)
        for i, v in config.ip_split_num_outlets.items():
            ip_splt_cfg[i] = self._split_cfg(unit_cfg, v)
        for i, v in config.lp_split_num_outlets.items():
            lp_splt_cfg[i] = self._split_cfg(unit_cfg, v)
        # put in splitters for turbine steam extractions
        if config.hp_split_locations:
            self.hp_split = Separator(
                config.hp_split_locations,
                default=s_sfg_default,
                initialize=hp_splt_cfg
            )
        if config.ip_split_locations:
            self.ip_split = Separator(
                config.ip_split_locations,
                default=s_sfg_default,
                initialize=ip_splt_cfg
            )
        if config.lp_split_locations:
            self.lp_split = Separator(
                config.lp_split_locations,
                default=s_sfg_default,
                initialize=lp_splt_cfg
            )

        # Done with unit models.  Adding Arcs (streams).
        # ------------------------------------------------

        # First up add streams in the inlet section
        def _split_to_rule(b, i):
            return {
                "source": getattr(self.inlet_split, "outlet_{}".format(i)),
                "destination": self.throttle_valve[i].inlet,
            }

        def _valve_to_rule(b, i):
            return {
                "source": self.throttle_valve[i].outlet,
                "destination": self.inlet_stage[i].inlet,
            }

        def _inlet_to_rule(b, i):
            return {
                "source": self.inlet_stage[i].outlet,
                "destination": getattr(self.inlet_mix, "inlet_{}".format(i)),
            }

        self.split_to_valve_stream = Arc(inlet_idx, rule=_split_to_rule)
        self.valve_to_inlet_stage_stream = Arc(inlet_idx, rule=_valve_to_rule)
        self.inlet_stage_to_mix = Arc(inlet_idx, rule=_inlet_to_rule)

        # There are three sections HP, IP, and LP which all have the same sort
        # of internal connctions, so the functions below provide some generic
        # capcbilities for adding the internal Arcs (streams).
        def _arc_indexes(nstages, index_set, discon, splits):
            """
            This takes the index set of all possible streams in a turbine
            section and throws out arc indexes for stages that are disconnected
            and arc indexes that are not needed because there is no splitter
            after a stage.

            Args:
                nstages (int): Number of stages in section
                index_set (Set): Index set for arcs in the section
                discon (list): Disconnected stages in the section
                splits (list): Spliter locations
            """
            sr = set()  # set of things to remove from the Arc index set
            for i in index_set:
                if (i[0] in discon or i[0] == nstages) and i[0] in splits:
                    # don't connect stage i to next remove stream after split
                    sr.add((i[0], 2))
                elif ((i[0] in discon or i[0] == nstages) and
                      i[0] not in splits):
                    # no splitter and disconnect so remove both streams
                    sr.add((i[0], 1))
                    sr.add((i[0], 2))
                elif i[0] not in splits:
                    # no splitter and not disconnected so just second stream
                    sr.add((i[0], 2))
                else:
                    # has splitter so need both streams don't remove anything
                    pass
            for i in sr:  # remove the unneeded Arc indexes
                index_set.remove(i)

        def _arc_rule(turbines, splitters):
            """
            This creates a rule function for arcs in a turbine section. When
            this is used the indexes for nonexistant stream will have already
            been removed, so any indexes the rule will get should have a stream
            associated.

            Args:
                turbines (TurbineStage): Indexed block with turbine section
                stages splitters (Separator): Indexed block of splitters
            """

            def _rule(b, i, j):
                if i in splitters and j == 1:
                    return {
                        "source": turbines[i].outlet,
                        "destination": splitters[i].inlet,
                    }
                elif j == 2:
                    return {
                        "source": splitters[i].outlet_1,
                        "destination": turbines[i + 1].inlet,
                    }
                else:
                    return {
                        "source": turbines[i].outlet,
                        "destination": turbines[i + 1].inlet,
                    }

            return _rule

        # Create initial arcs index sets with all possible streams
        self.hp_stream_idx = Set(
            initialize=self.hp_stages.index_set() * [1, 2])
        self.ip_stream_idx = Set(
            initialize=self.ip_stages.index_set() * [1, 2])
        self.lp_stream_idx = Set(
            initialize=self.lp_stages.index_set() * [1, 2])

        # Throw out unneeded streams
        _arc_indexes(
            config.num_hp,
            self.hp_stream_idx,
            config.hp_disconnect,
            config.hp_split_locations,
        )
        _arc_indexes(
            config.num_ip,
            self.ip_stream_idx,
            config.ip_disconnect,
            config.ip_split_locations,
        )
        _arc_indexes(
            config.num_lp,
            self.lp_stream_idx,
            config.lp_disconnect,
            config.lp_split_locations,
        )

        # Create connections internal to each turbine section (hp, ip, and lp)
        self.hp_stream = Arc(
            self.hp_stream_idx, rule=_arc_rule(self.hp_stages, self.hp_split)
        )
        self.ip_stream = Arc(
            self.ip_stream_idx, rule=_arc_rule(self.ip_stages, self.ip_split)
        )
        self.lp_stream = Arc(
            self.lp_stream_idx, rule=_arc_rule(self.lp_stages, self.lp_split)
        )

        # Connect hp section to ip section unless its a disconnect location
        last_hp = config.num_hp
        if (0 not in config.ip_disconnect and
                last_hp not in config.hp_disconnect):
            if last_hp in config.hp_split_locations:  # connect splitter to ip
                self.hp_to_ip_stream = Arc(
                    source=self.hp_split[last_hp].outlet_1,
                    destination=self.ip_stages[1].inlet,
                )
            else:  # connect last hp to ip
                self.hp_to_ip_stream = Arc(
                    source=self.hp_stages[last_hp].outlet,
                    destination=self.ip_stages[1].inlet,
                )
        # Connect ip section to lp section unless its a disconnect location
        last_ip = config.num_ip
        if (0 not in config.lp_disconnect and
                last_ip not in config.ip_disconnect):
            if last_ip in config.ip_split_locations:  # connect splitter to ip
                self.ip_to_lp_stream = Arc(
                    source=self.ip_split[last_ip].outlet_1,
                    destination=self.lp_stages[1].inlet,
                )
            else:  # connect last hp to ip
                self.ip_to_lp_stream = Arc(
                    source=self.ip_stages[last_ip].outlet,
                    destination=self.lp_stages[1].inlet,
                )
        # Connect inlet stage to hp section
        #   not allowing disconnection of inlet and first regular hp stage
        if 0 in config.hp_split_locations:
            # connect inlet mix to splitter and splitter to hp section
            self.inlet_to_splitter_stream = Arc(
                source=self.inlet_mix.outlet,
                destination=self.hp_split[0].inlet
            )
            self.splitter_to_hp_stream = Arc(
                source=self.hp_split[0].outlet_1,
                destination=self.hp_stages[1].inlet
            )
        else:  # connect mixer to first hp turbine stage
            self.inlet_to_hp_stream = Arc(
                source=self.inlet_mix.outlet,
                destination=self.hp_stages[1].inlet
            )

        @self.Expression(self.flowsheet().config.time)
        def power(b, t):
            return (
                sum(b.inlet_stage[i].power_shaft[t] for i in b.inlet_stage)
                + b.outlet_stage.power_shaft[t]
                + sum(b.hp_stages[i].power_shaft[t] for i in b.hp_stages)
                + sum(b.ip_stages[i].power_shaft[t] for i in b.ip_stages)
                + sum(b.lp_stages[i].power_shaft[t] for i in b.lp_stages)
            )

        # Connect inlet stage to hp section
        #   not allowing disconnection of inlet and first regular hp stage
        last_lp = config.num_lp
        if last_lp in config.lp_split_locations:  # connect splitter to outlet
            self.lp_to_outlet_stream = Arc(
                source=self.lp_split[last_lp].outlet_1,
                destination=self.outlet_stage.inlet,
            )
        else:  # connect last lpstage to outlet
            self.lp_to_outlet_stream = Arc(
                source=self.lp_stages[last_lp].outlet,
                destination=self.outlet_stage.inlet,
            )
        TransformationFactory("network.expand_arcs").apply_to(self)
Esempio n. 13
0
class TurbineMultistageData(UnitModelBlockData):

    CONFIG = ConfigBlock()
    _define_turbine_multistage_config(CONFIG)

    def build(self):
        super(TurbineMultistageData, self).build()
        config = self.config
        unit_cfg = {  # general unit model config
            "dynamic": config.dynamic,
            "has_holdup": config.has_holdup,
            "has_phase_equilibrium": config.has_phase_equilibrium,
            "material_balance_type": config.material_balance_type,
            "property_package": config.property_package,
            "property_package_args": config.property_package_args,
        }
        ni = self.config.num_parallel_inlet_stages
        inlet_idx = self.inlet_stage_idx = RangeSet(ni)

        # Adding unit models
        # ------------------------

        # Splitter to inlet that splits main flow into parallel flows for
        # paritial arc admission to the turbine
        self.inlet_split = Separator(default=self._split_cfg(unit_cfg, ni))
        self.throttle_valve = SteamValve(inlet_idx, default=unit_cfg)
        self.inlet_stage = TurbineInletStage(inlet_idx, default=unit_cfg)
        # mixer to combine the parallel flows back together
        self.inlet_mix = Mixer(default=self._mix_cfg(unit_cfg, ni))
        # add turbine sections.
        # inlet stage -> hp stages -> ip stages -> lp stages -> outlet stage
        self.hp_stages = TurbineStage(
            RangeSet(config.num_hp), default=unit_cfg)
        self.ip_stages = TurbineStage(
            RangeSet(config.num_ip), default=unit_cfg)
        self.lp_stages = TurbineStage(
            RangeSet(config.num_lp), default=unit_cfg)
        self.outlet_stage = TurbineOutletStage(default=unit_cfg)

        for i in self.hp_stages:
            self.hp_stages[i].ratioP.fix()
            self.hp_stages[i].efficiency_isentropic[:].fix()
        for i in self.ip_stages:
            self.ip_stages[i].ratioP.fix()
            self.ip_stages[i].efficiency_isentropic[:].fix()
        for i in self.lp_stages:
            self.lp_stages[i].ratioP.fix()
            self.lp_stages[i].efficiency_isentropic[:].fix()

        # Then make splitter config.  If number of outlets is specified
        # make a specific config, otherwise use default with 2 outlets
        s_sfg_default = self._split_cfg(unit_cfg, 2)
        hp_splt_cfg = {}
        ip_splt_cfg = {}
        lp_splt_cfg = {}
        # Now to finish up if there are more than two outlets, set that
        for i, v in config.hp_split_num_outlets.items():
            hp_splt_cfg[i] = self._split_cfg(unit_cfg, v)
        for i, v in config.ip_split_num_outlets.items():
            ip_splt_cfg[i] = self._split_cfg(unit_cfg, v)
        for i, v in config.lp_split_num_outlets.items():
            lp_splt_cfg[i] = self._split_cfg(unit_cfg, v)
        # put in splitters for turbine steam extractions
        if config.hp_split_locations:
            self.hp_split = Separator(
                config.hp_split_locations,
                default=s_sfg_default,
                initialize=hp_splt_cfg
            )
        if config.ip_split_locations:
            self.ip_split = Separator(
                config.ip_split_locations,
                default=s_sfg_default,
                initialize=ip_splt_cfg
            )
        if config.lp_split_locations:
            self.lp_split = Separator(
                config.lp_split_locations,
                default=s_sfg_default,
                initialize=lp_splt_cfg
            )

        # Done with unit models.  Adding Arcs (streams).
        # ------------------------------------------------

        # First up add streams in the inlet section
        def _split_to_rule(b, i):
            return {
                "source": getattr(self.inlet_split, "outlet_{}".format(i)),
                "destination": self.throttle_valve[i].inlet,
            }

        def _valve_to_rule(b, i):
            return {
                "source": self.throttle_valve[i].outlet,
                "destination": self.inlet_stage[i].inlet,
            }

        def _inlet_to_rule(b, i):
            return {
                "source": self.inlet_stage[i].outlet,
                "destination": getattr(self.inlet_mix, "inlet_{}".format(i)),
            }

        self.split_to_valve_stream = Arc(inlet_idx, rule=_split_to_rule)
        self.valve_to_inlet_stage_stream = Arc(inlet_idx, rule=_valve_to_rule)
        self.inlet_stage_to_mix = Arc(inlet_idx, rule=_inlet_to_rule)

        # There are three sections HP, IP, and LP which all have the same sort
        # of internal connctions, so the functions below provide some generic
        # capcbilities for adding the internal Arcs (streams).
        def _arc_indexes(nstages, index_set, discon, splits):
            """
            This takes the index set of all possible streams in a turbine
            section and throws out arc indexes for stages that are disconnected
            and arc indexes that are not needed because there is no splitter
            after a stage.

            Args:
                nstages (int): Number of stages in section
                index_set (Set): Index set for arcs in the section
                discon (list): Disconnected stages in the section
                splits (list): Spliter locations
            """
            sr = set()  # set of things to remove from the Arc index set
            for i in index_set:
                if (i[0] in discon or i[0] == nstages) and i[0] in splits:
                    # don't connect stage i to next remove stream after split
                    sr.add((i[0], 2))
                elif ((i[0] in discon or i[0] == nstages) and
                      i[0] not in splits):
                    # no splitter and disconnect so remove both streams
                    sr.add((i[0], 1))
                    sr.add((i[0], 2))
                elif i[0] not in splits:
                    # no splitter and not disconnected so just second stream
                    sr.add((i[0], 2))
                else:
                    # has splitter so need both streams don't remove anything
                    pass
            for i in sr:  # remove the unneeded Arc indexes
                index_set.remove(i)

        def _arc_rule(turbines, splitters):
            """
            This creates a rule function for arcs in a turbine section. When
            this is used the indexes for nonexistant stream will have already
            been removed, so any indexes the rule will get should have a stream
            associated.

            Args:
                turbines (TurbineStage): Indexed block with turbine section
                stages splitters (Separator): Indexed block of splitters
            """

            def _rule(b, i, j):
                if i in splitters and j == 1:
                    return {
                        "source": turbines[i].outlet,
                        "destination": splitters[i].inlet,
                    }
                elif j == 2:
                    return {
                        "source": splitters[i].outlet_1,
                        "destination": turbines[i + 1].inlet,
                    }
                else:
                    return {
                        "source": turbines[i].outlet,
                        "destination": turbines[i + 1].inlet,
                    }

            return _rule

        # Create initial arcs index sets with all possible streams
        self.hp_stream_idx = Set(
            initialize=self.hp_stages.index_set() * [1, 2])
        self.ip_stream_idx = Set(
            initialize=self.ip_stages.index_set() * [1, 2])
        self.lp_stream_idx = Set(
            initialize=self.lp_stages.index_set() * [1, 2])

        # Throw out unneeded streams
        _arc_indexes(
            config.num_hp,
            self.hp_stream_idx,
            config.hp_disconnect,
            config.hp_split_locations,
        )
        _arc_indexes(
            config.num_ip,
            self.ip_stream_idx,
            config.ip_disconnect,
            config.ip_split_locations,
        )
        _arc_indexes(
            config.num_lp,
            self.lp_stream_idx,
            config.lp_disconnect,
            config.lp_split_locations,
        )

        # Create connections internal to each turbine section (hp, ip, and lp)
        self.hp_stream = Arc(
            self.hp_stream_idx, rule=_arc_rule(self.hp_stages, self.hp_split)
        )
        self.ip_stream = Arc(
            self.ip_stream_idx, rule=_arc_rule(self.ip_stages, self.ip_split)
        )
        self.lp_stream = Arc(
            self.lp_stream_idx, rule=_arc_rule(self.lp_stages, self.lp_split)
        )

        # Connect hp section to ip section unless its a disconnect location
        last_hp = config.num_hp
        if (0 not in config.ip_disconnect and
                last_hp not in config.hp_disconnect):
            if last_hp in config.hp_split_locations:  # connect splitter to ip
                self.hp_to_ip_stream = Arc(
                    source=self.hp_split[last_hp].outlet_1,
                    destination=self.ip_stages[1].inlet,
                )
            else:  # connect last hp to ip
                self.hp_to_ip_stream = Arc(
                    source=self.hp_stages[last_hp].outlet,
                    destination=self.ip_stages[1].inlet,
                )
        # Connect ip section to lp section unless its a disconnect location
        last_ip = config.num_ip
        if (0 not in config.lp_disconnect and
                last_ip not in config.ip_disconnect):
            if last_ip in config.ip_split_locations:  # connect splitter to ip
                self.ip_to_lp_stream = Arc(
                    source=self.ip_split[last_ip].outlet_1,
                    destination=self.lp_stages[1].inlet,
                )
            else:  # connect last hp to ip
                self.ip_to_lp_stream = Arc(
                    source=self.ip_stages[last_ip].outlet,
                    destination=self.lp_stages[1].inlet,
                )
        # Connect inlet stage to hp section
        #   not allowing disconnection of inlet and first regular hp stage
        if 0 in config.hp_split_locations:
            # connect inlet mix to splitter and splitter to hp section
            self.inlet_to_splitter_stream = Arc(
                source=self.inlet_mix.outlet,
                destination=self.hp_split[0].inlet
            )
            self.splitter_to_hp_stream = Arc(
                source=self.hp_split[0].outlet_1,
                destination=self.hp_stages[1].inlet
            )
        else:  # connect mixer to first hp turbine stage
            self.inlet_to_hp_stream = Arc(
                source=self.inlet_mix.outlet,
                destination=self.hp_stages[1].inlet
            )

        @self.Expression(self.flowsheet().config.time)
        def power(b, t):
            return (
                sum(b.inlet_stage[i].power_shaft[t] for i in b.inlet_stage)
                + b.outlet_stage.power_shaft[t]
                + sum(b.hp_stages[i].power_shaft[t] for i in b.hp_stages)
                + sum(b.ip_stages[i].power_shaft[t] for i in b.ip_stages)
                + sum(b.lp_stages[i].power_shaft[t] for i in b.lp_stages)
            )

        # Connect inlet stage to hp section
        #   not allowing disconnection of inlet and first regular hp stage
        last_lp = config.num_lp
        if last_lp in config.lp_split_locations:  # connect splitter to outlet
            self.lp_to_outlet_stream = Arc(
                source=self.lp_split[last_lp].outlet_1,
                destination=self.outlet_stage.inlet,
            )
        else:  # connect last lpstage to outlet
            self.lp_to_outlet_stream = Arc(
                source=self.lp_stages[last_lp].outlet,
                destination=self.outlet_stage.inlet,
            )
        TransformationFactory("network.expand_arcs").apply_to(self)

    def _split_cfg(self, unit_cfg, no=2):
        """
        This creates a configuration dictionary for a splitter.

        Args:
            unit_cfg: The base unit config dict.
            no: Number of outlets, default=2
        """
        # Create a dict for splitter config args
        s_cfg = copy.copy(unit_cfg)  # splitter config based on unit_cfg
        s_cfg.update(
            split_basis=SplittingType.totalFlow,
            ideal_separation=False,
            num_outlets=no,
            energy_split_basis=EnergySplittingType.equal_molar_enthalpy,
        )
        return s_cfg

    def _mix_cfg(self, unit_cfg, ni=2):
        """
        This creates a configuration dictionary for a mixer.

        Args:
            unit_cfg: The base unit config dict.
            ni: Number of inlets, default=2
        """
        m_cfg = copy.copy(unit_cfg)  # splitter config based on unit_cfg
        m_cfg.update(
            num_inlets=ni,
            momentum_mixing_type=MomentumMixingType.minimize_and_equality
        )
        return m_cfg

    def throttle_cv_fix(self, value):
        """
        Fix the thottle valve coefficients.  These are generally the same for
        each of the parallel stages so this provides a convenient way to set
        them.

        Args:
            value: The value to fix the turbine inlet flow coefficients at
        """
        for i in self.throttle_valve:
            self.throttle_valve[i].Cv.fix(value)

    def turbine_inlet_cf_fix(self, value):
        """
        Fix the inlet turbine stage flow coefficient.  These are
        generally the same for each of the parallel stages so this provides
        a convenient way to set them.

        Args:
            value: The value to fix the turbine inlet flow coefficients at
        """
        for i in self.inlet_stage:
            self.inlet_stage[i].flow_coeff.fix(value)

    def turbine_outlet_cf_fix(self, value):
        """
        Fix the inlet turbine stage flow coefficient.  These are
        generally the same for each of the parallel stages so this provides
        a convenient way to set them.

        Args:
            value: The value to fix the turbine inlet flow coefficients at
        """
        self.outlet_stage.flow_coeff.fix(value)

    def initialize(
        self,
        outlvl=idaeslog.NOTSET,
        solver="ipopt",
        optarg={"tol": 1e-6, "max_iter": 35},
        copy_disconneted_flow=True,
    ):
        """
        Initialize
        """
        # sp is what to save to make sure state after init is same as the start
        #   saves value, fixed, and active state, doesn't load originally free
        #   values, this makes sure original problem spec is same but
        #   initializes the values of free vars
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)
        ni = self.config.num_parallel_inlet_stages

        def init_section(stages, splits, disconnects, prev_port):
            if 0 in splits:
                _set_port(splits[0].inlet, prev_port)
                splits[0].initialize(
                    outlvl=outlvl, solver=solver, optarg=optarg)
                prev_port = splits[0].outlet_1
            for i in stages:
                if i - 1 not in disconnects:
                    _set_port(stages[i].inlet, prev_port)
                else:
                    if copy_disconneted_flow:
                        for t in stages[i].stages[i].inlet.flow_mol:
                            stages[i].inlet.flow_mol[t] = \
                                prev_port.flow_mol[t]
                stages[i].initialize(
                    outlvl=outlvl, solver=solver, optarg=optarg)
                prev_port = stages[i].outlet
                if i in splits:
                    _set_port(splits[i].inlet, prev_port)
                    splits[i].initialize(
                        outlvl=outlvl, solver=solver, optarg=optarg)
                    prev_port = splits[i].outlet_1
            return prev_port

        for k in [1, 2]:
            # Initialize Splitter
            # Fix n - 1 split fractions
            self.inlet_split.split_fraction[0, "outlet_1"].value = 1.0 / ni
            for i in self.inlet_stage_idx:
                if i == 1:  # fix rest of splits at leaving first one free
                    continue
                self.inlet_split.split_fraction[
                    0, "outlet_{}".format(i)].fix(1.0 / ni)
            # fix inlet and free outlet
            self.inlet_split.inlet.fix()
            for i in self.inlet_stage_idx:
                ol = getattr(self.inlet_split, "outlet_{}".format(i))
                ol.unfix()
            self.inlet_split.initialize(
                outlvl=outlvl, solver=solver, optarg=optarg)
            # free split fractions
            for i in self.inlet_stage_idx:
                self.inlet_split.split_fraction[
                    0, "outlet_{}".format(i)].unfix()

            # Initialize valves
            for i in self.inlet_stage_idx:
                _set_port(
                    self.throttle_valve[i].inlet,
                    getattr(self.inlet_split, "outlet_{}".format(i)),
                )
                self.throttle_valve[i].initialize(
                    outlvl=outlvl, solver=solver, optarg=optarg
                )

            # Initialize turbine
            for i in self.inlet_stage_idx:
                _set_port(
                    self.inlet_stage[i].inlet, self.throttle_valve[i].outlet)
                self.inlet_stage[i].initialize(
                    outlvl=outlvl, solver=solver, optarg=optarg
                )

            # Initialize Mixer
            self.inlet_mix.use_minimum_inlet_pressure_constraint()
            for i in self.inlet_stage_idx:
                _set_port(
                    getattr(self.inlet_mix, "inlet_{}".format(i)),
                    self.inlet_stage[i].outlet,
                )
                getattr(self.inlet_mix, "inlet_{}".format(i)).fix()
            self.inlet_mix.initialize(
                outlvl=outlvl, solver=solver, optarg=optarg)
            for i in self.inlet_stage_idx:
                getattr(self.inlet_mix, "inlet_{}".format(i)).unfix()
            self.inlet_mix.use_equal_pressure_constraint()

            prev_port = self.inlet_mix.outlet
            prev_port = init_section(
                self.hp_stages,
                self.hp_split,
                self.config.hp_disconnect,
                prev_port
            )
            if len(self.hp_stages) in self.config.hp_disconnect:
                prev_port = self.ip_stages[1].inlet
            prev_port = init_section(
                self.ip_stages,
                self.ip_split,
                self.config.ip_disconnect,
                prev_port
            )
            if len(self.ip_stages) in self.config.ip_disconnect:
                prev_port = self.lp_stages[1].inlet
            prev_port = init_section(
                self.lp_stages,
                self.lp_split,
                self.config.lp_disconnect,
                prev_port
            )

            _set_port(self.outlet_stage.inlet, prev_port)
            self.outlet_stage.initialize(
                outlvl=outlvl, solver=solver, optarg=optarg)
            for t in self.flowsheet().time:
                self.inlet_split.inlet.flow_mol[
                    t
                ].value = self.outlet_stage.inlet.flow_mol[t].value

        from_json(self, sd=istate, wts=sp)
Esempio n. 14
0
def create_model():
    m = ConcreteModel()

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

    # Add properties for PEM
    m.fs.PEM_properties = GenericParameterBlock(default=configuration)

    # Add properties for hydrogen turbine
    m.fs.h2turbine_props = GenericParameterBlock(default=configuration1)

    m.fs.reaction_params = reaction_props.H2ReactionParameterBlock(
        default={"property_package": m.fs.h2turbine_props})

    # Add the PEM electrolyzer unit
    m.fs.pem = PEM_Electrolyzer(
        default={"property_package": m.fs.PEM_properties})

    # Add translator block
    m.fs.translator = Translator(
        default={
            "inlet_property_package": m.fs.PEM_properties,
            "outlet_property_package": m.fs.h2turbine_props
        })

    # Add mixer block
    m.fs.mixer = Mixer(
        default={
            "property_package": m.fs.h2turbine_props,
            "inlet_list": ["air_feed", "hydrogen_feed"]
        })

    # Add the hydrogen turbine
    m.fs.h2_turbine = HydrogenTurbine(
        default={
            "property_package": m.fs.h2turbine_props,
            "reaction_package": m.fs.reaction_params
        })

    m.fs.H2_mass = 2.016 / 1000

    m.fs.H2_production = Expression(expr=m.fs.pem.outlet.flow_mol[0] *
                                    m.fs.H2_mass)

    # Add translator constraints
    # Set hydrogen flow and mole frac
    m.fs.translator.eq_flow_hydrogen = Constraint(
        expr=m.fs.translator.inlet.flow_mol[0] ==
        m.fs.translator.outlet.flow_mol[0])

    m.fs.translator.mole_frac_hydrogen = Constraint(
        expr=m.fs.translator.outlet.mole_frac_comp[0, "hydrogen"] == 0.99)

    m.fs.translator.eq_temperature = Constraint(
        expr=m.fs.translator.inlet.temperature[0] ==
        m.fs.translator.outlet.temperature[0])

    m.fs.translator.eq_pressure = Constraint(
        expr=m.fs.translator.inlet.pressure[0] ==
        m.fs.translator.outlet.pressure[0])

    m.fs.translator.outlet.mole_frac_comp[0, "oxygen"].fix(0.01 / 4)
    m.fs.translator.outlet.mole_frac_comp[0, "argon"].fix(0.01 / 4)
    m.fs.translator.outlet.mole_frac_comp[0, "nitrogen"].fix(0.01 / 4)
    m.fs.translator.outlet.mole_frac_comp[0, "water"].fix(0.01 / 4)

    # add arcs
    m.fs.pem_to_translator = Arc(source=m.fs.pem.outlet,
                                 destination=m.fs.translator.inlet)

    # add arcs
    m.fs.translator_to_mixer = Arc(source=m.fs.translator.outlet,
                                   destination=m.fs.mixer.hydrogen_feed)

    # add arcs
    m.fs.mixer_to_turbine = Arc(source=m.fs.mixer.outlet,
                                destination=m.fs.h2_turbine.compressor.inlet)

    # expand arcs
    TransformationFactory("network.expand_arcs").apply_to(m)

    return m
Esempio n. 15
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. 16
0
def build_desalination(
    m,
    has_desal_feed=False,
    is_twostage=False,
    has_ERD=False,
    RO_type="0D",
    RO_base="TDS",
    RO_level="simple",
):
    """
    Builds RO desalination including specified feed and auxiliary equipment.

    Args:
        has_desal_feed: True or False, default = False,
            if True a feed block is created and specified to the standard feed
        RO_type: 'Sep', '0D', or 1D, default = '0D'
        RO_level: 'simple' or 'detailed', default = 'simple'
        RO_base: 'TDS' only, default = 'ion'
    """
    desal_port = {}
    prop = property_models.get_prop(m, base=RO_base)

    if has_desal_feed:
        # build feed
        feed_block.build_feed(m, base=RO_base)

    # build RO
    if RO_type == "Sep":
        unit_separator.build_SepRO(m, base=RO_base)
    elif RO_type == "0D":
        unit_0DRO.build_RO(m, base="TDS", level=RO_level)
    elif RO_type == "1D":
        unit_1DRO.build_RO(m, base="TDS", level=RO_level)
    else:
        raise ValueError(
            "Unexpected model type {RO_type} provided to build_desalination"
            "".format(RO_type=RO_type))
    if is_twostage:
        if RO_type == "0D":
            unit_0DRO.build_RO(m, base="TDS", level=RO_level, name_str="RO2")
        elif RO_type == "1D":
            unit_1DRO.build_RO(m, base="TDS", level=RO_level, name_str="RO2")
        else:
            raise ValueError(
                "Unexpected model type {RO_type} provided to build_desalination when is_twostage is True"
                "".format(RO_type=RO_type))

    if has_ERD:
        if RO_type == "Sep":
            raise ValueError(
                "Unexpected model type {RO_type} provided to build_desalination when has_ERD is True"
                "".format(RO_type=RO_type))
        m.fs.ERD = EnergyRecoveryDevice(default={"property_package": prop})

    # auxiliary units
    if RO_type == "Sep":
        # build auxiliary units (none)

        # connect models
        if has_desal_feed:
            m.fs.s_desal_feed_RO = Arc(source=m.fs.feed.outlet,
                                       destination=m.fs.RO.inlet)

        # specify (RO already specified)

        # inlet/outlet ports for pretreatment
        if not has_desal_feed:
            desal_port["in"] = m.fs.RO.inlet

    elif RO_type == "0D" or RO_type == "1D":
        # build auxiliary units
        m.fs.pump_RO = Pump(default={"property_package": prop})
        if is_twostage:
            m.fs.pump_RO2 = Pump(default={"property_package": prop})
            m.fs.mixer_permeate = Mixer(default={
                "property_package": prop,
                "inlet_list": ["RO", "RO2"]
            })

        # connect models
        if has_desal_feed:
            m.fs.s_desal_feed_pumpRO = Arc(source=m.fs.feed.outlet,
                                           destination=m.fs.pump_RO.inlet)

        m.fs.s_desal_pumpRO_RO = Arc(source=m.fs.pump_RO.outlet,
                                     destination=m.fs.RO.inlet)

        if is_twostage:
            m.fs.s_desal_RO_pumpRO2 = Arc(source=m.fs.RO.retentate,
                                          destination=m.fs.pump_RO2.inlet)
            m.fs.s_desal_pumpRO2_RO2 = Arc(source=m.fs.pump_RO2.outlet,
                                           destination=m.fs.RO2.inlet)
            m.fs.s_desal_permeateRO_mixer = Arc(
                source=m.fs.RO.permeate, destination=m.fs.mixer_permeate.RO)
            m.fs.s_desal_permeateRO2_mixer = Arc(
                source=m.fs.RO2.permeate, destination=m.fs.mixer_permeate.RO2)

        if has_ERD:
            if is_twostage:
                m.fs.s_desal_RO2_ERD = Arc(source=m.fs.RO2.retentate,
                                           destination=m.fs.ERD.inlet)
            else:
                m.fs.s_desal_RO_ERD = Arc(source=m.fs.RO.retentate,
                                          destination=m.fs.ERD.inlet)

        # specify (RO already specified, Pump 2 DOF, ERD 2 DOF)
        m.fs.pump_RO.efficiency_pump.fix(0.80)
        m.fs.pump_RO.control_volume.properties_out[0].pressure.fix(50e5)
        if is_twostage:
            m.fs.pump_RO2.efficiency_pump.fix(0.80)
            m.fs.pump_RO2.control_volume.properties_out[0].pressure.fix(55e5)

        if has_ERD:
            m.fs.ERD.efficiency_pump.fix(0.95)
            m.fs.ERD.outlet.pressure[0].fix(101325)

        # inlet/outlet ports for pretreatment
        if not has_desal_feed:
            desal_port["in"] = m.fs.pump_RO.inlet

    if is_twostage:
        desal_port["out"] = m.fs.mixer_permeate.outlet
        if has_ERD:
            desal_port["waste"] = m.fs.ERD.outlet
        else:
            desal_port["waste"] = m.fs.RO2.retentate
    else:
        desal_port["out"] = m.fs.RO.permeate
        if has_ERD:
            desal_port["waste"] = m.fs.ERD.outlet
        else:
            desal_port["waste"] = m.fs.RO.retentate

    return desal_port
Esempio n. 17
0
def build_ne_flowsheet(m, **kwargs):
    """
    This function builds the entire nuclear flowsheet by adding the
    required models and arcs connecting the models.
    """
    m.fs = FlowsheetBlock(default={"dynamic": False})

    # Load thermodynamic and reaction packages
    m.fs.h2ideal_props = GenericParameterBlock(default=h2_ideal_config)
    m.fs.h2turbine_props = GenericParameterBlock(default=hturbine_config)
    m.fs.reaction_params = h2_reaction_props.H2ReactionParameterBlock(
        default={"property_package": m.fs.h2turbine_props})

    # Add electrical splitter
    m.fs.np_power_split = ElectricalSplitter(default={
        "num_outlets": 2,
        "outlet_list": ["np_to_grid", "np_to_pem"]})

    # Add PEM electrolyzer
    m.fs.pem = PEM_Electrolyzer(default={
        "property_package": m.fs.h2ideal_props})

    # Add hydrogen tank
    m.fs.h2_tank = SimpleHydrogenTank(default={
        "property_package": m.fs.h2ideal_props})

    # Add translator block
    m.fs.translator = Translator(default={
        "inlet_property_package": m.fs.h2ideal_props,
        "outlet_property_package": m.fs.h2turbine_props})

    # Add translator block constraints
    m.fs.translator.eq_flow_hydrogen = Constraint(
        expr=m.fs.translator.inlet.flow_mol[0] ==
             m.fs.translator.outlet.flow_mol[0])

    m.fs.translator.eq_temperature = Constraint(
        expr=m.fs.translator.inlet.temperature[0] ==
             m.fs.translator.outlet.temperature[0])

    m.fs.translator.eq_pressure = Constraint(
        expr=m.fs.translator.inlet.pressure[0] ==
             m.fs.translator.outlet.pressure[0])

    # Add mixer block
    # using minimize pressure for all inlets and outlet of the mixer
    # because pressure of inlets is already fixed in flowsheet,
    # using equality will over-constrain
    m.fs.mixer = Mixer(default={
        "momentum_mixing_type": MomentumMixingType.minimize,
        "property_package": m.fs.h2turbine_props,
        "inlet_list": ["air_feed", "hydrogen_feed"]})

    # Add hydrogen turbine
    m.fs.h2_turbine = HydrogenTurbine(
        default={"property_package": m.fs.h2turbine_props,
                 "reaction_package": m.fs.reaction_params})

    """
    Connect the individual blocks via Arcs
    """
    # Connect the electrical splitter and PEM
    m.fs.arc_np_to_pem = Arc(
        source=m.fs.np_power_split.np_to_pem_port,
        destination=m.fs.pem.electricity_in
    )

    # Connect the pem electrolyzer and h2 tank
    m.fs.arc_pem_to_h2_tank = Arc(
        source=m.fs.pem.outlet,
        destination=m.fs.h2_tank.inlet
    )

    # Connect h2 tank and translator
    m.fs.arc_h2_tank_to_translator = Arc(
        source=m.fs.h2_tank.outlet_to_turbine,
        destination=m.fs.translator.inlet
    )

    # Connect translator and mixer
    m.fs.arc_translator_to_mixer = Arc(
        source=m.fs.translator.outlet,
        destination=m.fs.mixer.hydrogen_feed
    )

    # Connect mixer and h2 turbine
    m.fs.arc_mixer_to_h2_turbine = Arc(
        source=m.fs.mixer.outlet,
        destination=m.fs.h2_turbine.compressor.inlet
    )

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

    return m
Esempio n. 18
0
def build_pretreatment_NF(m, has_bypass=True, NF_type="ZO", NF_base="ion"):
    """
    Builds NF pretreatment including specified feed and auxiliary equipment.
    Arguments:
        has_bypass: True or False, default = True
        NF_type: 'Sep' or 'ZO', default = 'ZO'
        NF_base: 'ion' or 'salt', default = 'ion'
    """
    pretrt_port = {}
    prop = property_models.get_prop(m, base=NF_base)

    # build feed
    feed_block.build_feed(m, base=NF_base)

    # build NF
    if NF_type == "Sep":
        unit_separator.build_SepNF(m, base=NF_base)
    elif NF_type == "ZO":
        unit_ZONF.build_ZONF(m, base=NF_base)
        m.fs.pump_NF = Pump(default={"property_package": prop})
    else:
        raise ValueError(
            "Unexpected model type {NF_type} provided to build_NF_no_bypass"
            "".format(NF_type=NF_type)
        )

    if has_bypass:
        # build auxiliary units
        m.fs.splitter = Separator(
            default={
                "property_package": prop,
                "outlet_list": ["pretreatment", "bypass"],
                "split_basis": SplittingType.totalFlow,
                "energy_split_basis": EnergySplittingType.equal_temperature,
            }
        )
        m.fs.mixer = Mixer(
            default={"property_package": prop, "inlet_list": ["pretreatment", "bypass"]}
        )

        # connect models
        m.fs.s_pretrt_feed_splitter = Arc(
            source=m.fs.feed.outlet, destination=m.fs.splitter.inlet
        )
        m.fs.s_pretrt_splitter_mixer = Arc(
            source=m.fs.splitter.bypass, destination=m.fs.mixer.bypass
        )
        if NF_type == "ZO":
            m.fs.s_pretrt_splitter_pumpNF = Arc(
                source=m.fs.splitter.pretreatment, destination=m.fs.pump_NF.inlet
            )
            m.fs.s_pretrt_pumpNF_NF = Arc(
                source=m.fs.pump_NF.outlet, destination=m.fs.NF.inlet
            )
        else:
            m.fs.s_pretrt_splitter_NF = Arc(
                source=m.fs.splitter.pretreatment, destination=m.fs.NF.inlet
            )
        m.fs.s_pretrt_NF_mixer = Arc(
            source=m.fs.NF.permeate, destination=m.fs.mixer.pretreatment
        )

        # specify (NF and feed is already specified, mixer has 0 DOF, splitter has 1 DOF, NF pump has 2 DOF)
        # splitter
        m.fs.splitter.split_fraction[0, "bypass"].fix(0.1)
        if NF_type == "ZO":
            m.fs.pump_NF.efficiency_pump.fix(0.80)
            m.fs.pump_NF.control_volume.properties_out[0].pressure.fix(4e5)

        # inlet/outlet ports for pretreatment
        pretrt_port["out"] = m.fs.mixer.outlet
        pretrt_port["waste"] = m.fs.NF.retentate

    else:  # no bypass
        # build auxiliary units (none)

        # connect models
        if NF_type == "ZO":
            m.fs.s_pretrt_feed_pumpNF = Arc(
                source=m.fs.feed.outlet, destination=m.fs.pump_NF.inlet
            )
            m.fs.s_pretrt_pumpNF_NF = Arc(
                source=m.fs.pump_NF.outlet, destination=m.fs.NF.inlet
            )
            # TODO: should source be m.fs.pump_NF.outlet? Double-check here and other arcs with pump_NF
        else:
            m.fs.s_pretrt_feed_NF = Arc(
                source=m.fs.feed.outlet, destination=m.fs.NF.inlet
            )

        # specify (NF and feed are already specified, NF pump has 2 DOF)
        if NF_type == "ZO":
            m.fs.pump_NF.efficiency_pump.fix(0.80)
            m.fs.pump_NF.control_volume.properties_out[0].pressure.fix(4e5)

        # inlet/outlet ports for pretreatment
        pretrt_port["out"] = m.fs.NF.permeate
        pretrt_port["waste"] = m.fs.NF.retentate

    return pretrt_port
def make_model(horizon=6, ntfe=60, ntcp=2, inlet_E=11.91, inlet_S=12.92):

    time_set = [0, horizon]

    m = ConcreteModel(name='CSTR with level control')
    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={'has_holdup': True,
                              '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})
    # MomentumBalanceType.none used because the property package doesn't
    # include pressure.

    m.fs.mixer = Mixer(default={
        'property_package': m.fs.properties,
        'material_balance_type': MaterialBalanceType.componentTotal,
        'momentum_mixing_type': MomentumMixingType.none,
        # MomentumMixingType.none used because the property package doesn't
        # include pressure.
        'num_inlets': 2,
       'inlet_list': ['S_inlet', 'E_inlet']})
    # Allegedly the proper energy balance is being used...

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

    m.fs.pid = PIDBlock(default={'pv': m.fs.cstr.volume,
                                 'output': m.fs.cstr.outlet.flow_vol,
                                 'upper': 5.0,
                                 'lower': 0.5,
                                 'calculate_initial_integral': True,
                                 # ^ Why would initial integral be calculated
                                 # to be nonzero?
                                 'pid_form': PIDForm.velocity})

    m.fs.pid.gain.fix(-1.0)
    m.fs.pid.time_i.fix(0.1)
    m.fs.pid.time_d.fix(0.0)
    m.fs.pid.setpoint.fix(1.0)

    # Fix initial condition for volume:
    m.fs.cstr.volume.unfix()
    m.fs.cstr.volume[m.fs.time.first()].fix(1.0)

    # Fix initial conditions for other variables:
    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.001)
    # Note: Model does not solve when initial conditions are empty tank
    m.fs.cstr.control_volume.energy_holdup[m.fs.time.first(), 'aq'].fix(300)

    m.fs.mixer.E_inlet.conc_mol.fix(0)
    m.fs.mixer.S_inlet.conc_mol.fix(0)
    m.fs.mixer.E_inlet.conc_mol[:,'Solvent'].fix(1.)
    m.fs.mixer.S_inlet.conc_mol[:,'Solvent'].fix(1.)

    for t, j in m.fs.time*m.fs.properties.component_list:
        if j == 'E':
            m.fs.mixer.E_inlet.conc_mol[t, j].fix(inlet_E)
        elif j == 'S':
            m.fs.mixer.S_inlet.conc_mol[t, j].fix(inlet_S)

    m.fs.mixer.E_inlet.flow_vol.fix(0.1)
    m.fs.mixer.S_inlet.flow_vol.fix(2.1)

    # Specify a perturbation to substrate flow rate:
    for t in m.fs.time:
        if t < horizon/4:
            continue
        else:
            m.fs.mixer.S_inlet.flow_vol[t].fix(3.0)

    m.fs.mixer.E_inlet.temperature.fix(290)
    m.fs.mixer.S_inlet.temperature.fix(310)

    m.fs.inlet = Arc(source=m.fs.mixer.outlet, destination=m.fs.cstr.inlet)

    # Fix "initial condition" for outlet flow rate, as here it cannot be
    # specified by the PID controller
    m.fs.cstr.outlet.flow_vol[m.fs.time.first()].fix(2.2)

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

    return m
Esempio n. 20
0
def add_unit_models(m):
    fs = m.fs_main.fs_blr
    prop_water = m.fs_main.prop_water
    prop_gas = m.fs_main.prop_gas

    fs.num_mills = pyo.Var()
    fs.num_mills.fix(4)

    # 14 waterwall zones
    fs.ww_zones = pyo.RangeSet(14)

    # boiler based on surrogate
    fs.aBoiler = BoilerSurrogate(default={"dynamic": False,
                               "side_1_property_package": prop_gas,
                               "side_2_property_package": prop_gas,
                               "has_heat_transfer": False,
                               "has_pressure_change": False,
                               "has_holdup": False})

    # model a drum by a WaterFlash, a Mixer and a Drum model
    fs.aFlash = WaterFlash(default={"dynamic": False,
                               "property_package": prop_water,
                               "has_phase_equilibrium": False,
                               "has_heat_transfer": False,
                               "has_pressure_change": False})
    fs.aMixer = HelmMixer(default={"dynamic": False,
                              "property_package": prop_water,
                              "momentum_mixing_type": MomentumMixingType.equality,
                              "inlet_list": ["FeedWater", "SatWater"]})
    fs.aDrum = Drum1D(default={"property_package": prop_water,
                               "has_holdup": True,
                               "has_heat_transfer": True,
                               "has_pressure_change": True,
                               "finite_elements": 4,
                               "inside_diameter": 1.778,
                               "thickness": 0.127})
    fs.blowdown_split = HelmSplitter(
        default={
            "dynamic": False,
            "property_package": prop_water,
            "outlet_list": ["FW_Downcomer", "FW_Blowdown"],
        }
    )
    # downcomer
    fs.aDowncomer = Downcomer(default={
                               "dynamic": False,
                               "property_package": prop_water,
                               "has_holdup": True,
                               "has_heat_transfer": True,
                               "has_pressure_change": True})

    # 14 WaterwallSection units
    fs.Waterwalls = WaterwallSection(fs.ww_zones,
                               default={
                               "has_holdup": True,
                               "property_package": prop_water,
                               "has_equilibrium_reactions": False,
                               "has_heat_of_reaction": False,
                               "has_heat_transfer": True,
                               "has_pressure_change": True})

    # roof superheater
    fs.aRoof = SteamHeater(default={
                               "dynamic": False,
                               "property_package": prop_water,
                               "has_holdup": True,
                               "has_equilibrium_reactions": False,
                               "has_heat_of_reaction": False,
                               "has_heat_transfer": True,
                               "has_pressure_change": True,
                               "single_side_only" : True})

    # platen superheater
    fs.aPlaten = SteamHeater(default={
                               "dynamic": False,
                               "property_package": prop_water,
                               "has_holdup": True,
                               "has_equilibrium_reactions": False,
                               "has_heat_of_reaction": False,
                               "has_heat_transfer": True,
                               "has_pressure_change": True,
                               "single_side_only" : False})

    # 1st reheater
    fs.aRH1 = HeatExchangerCrossFlow2D(default={
                               "tube_side":{"property_package": prop_water, "has_holdup": False,
                                            "has_pressure_change": True},
                               "shell_side":{"property_package": prop_gas, "has_holdup": False,
                                             "has_pressure_change": True},
                               "finite_elements": 4,
                               "flow_type": "counter_current",
                               "tube_arrangement": "in-line",
                               "tube_side_water_phase": "Vap",
                               "has_radiation": True,
                               "radial_elements": 5,
                               "inside_diameter": 2.202*0.0254,
                               "thickness": 0.149*0.0254})

    # 2nd reheater
    fs.aRH2 = HeatExchangerCrossFlow2D(default={
                               "tube_side":{"property_package": prop_water, "has_holdup": False,
                                            "has_pressure_change": True},
                               "shell_side":{"property_package": prop_gas, "has_holdup": False,
                                             "has_pressure_change": True},
                               "finite_elements": 2,
                               "flow_type": "counter_current",
                               "tube_arrangement": "in-line",
                               "tube_side_water_phase": "Vap",
                               "has_radiation": True,
                               "radial_elements": 5,
                               "inside_diameter": 2.217*0.0254,
                               "thickness": 0.1415*0.0254})

    # primary superheater
    fs.aPSH = HeatExchangerCrossFlow2D(default={
                               "tube_side":{"property_package": prop_water, "has_holdup": False,
                                            "has_pressure_change": True},
                               "shell_side":{"property_package": prop_gas, "has_holdup": False,
                                             "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,
                               "inside_diameter": 1.45*0.0254,
                               "thickness": 0.15*0.0254})

    # economizer
    fs.aECON = HeatExchangerCrossFlow2D(default={
                               "tube_side":{"property_package": prop_water, "has_holdup": False,
                                            "has_pressure_change": True},
                               "shell_side":{"property_package": prop_gas, "has_holdup": False,
                                             "has_pressure_change": True},
                               "finite_elements": 5,
                               "flow_type": "counter_current",
                               "tube_arrangement": "in-line",
                               "tube_side_water_phase": "Liq",
                               "has_radiation": False,
                               "radial_elements": 5,
                               "inside_diameter": 1.452*0.0254,
                               "thickness": 0.149*0.0254})

    # water pipe from economizer outlet to drum
    fs.aPipe = WaterPipe(default={
                               "dynamic": False,
                               "property_package": prop_water,
                               "has_holdup": True,
                               "has_heat_transfer": False,
                               "has_pressure_change": True,
                               "water_phase": 'Liq',
                               "contraction_expansion_at_end": 'None'})

    # a mixer to mix hot primary air with tempering air
    fs.Mixer_PA = Mixer(
        default={
            "dynamic": False,
            "property_package": prop_gas,
            "momentum_mixing_type": MomentumMixingType.equality,
            "inlet_list": ["PA_inlet", "TA_inlet"],
        }
    )

    # attemperator for main steam before platen SH
    fs.Attemp = HelmMixer(
        default={
            "dynamic": False,
            "property_package": prop_water,
            "momentum_mixing_type": MomentumMixingType.equality,
            "inlet_list": ["Steam_inlet", "Water_inlet"],
        }
    )

    # air preheater as three-stream heat exchanger with heat loss to ambient,
    #     side_1: flue gas
    #     side_2:PA (priamry air?)
    #     side_3:PA (priamry air?)
    fs.aAPH = HeatExchangerWith3Streams(
        default={"dynamic": False,
            "side_1_property_package": prop_gas,
            "side_2_property_package": prop_gas,
            "side_3_property_package": prop_gas,
            "has_heat_transfer": True,
            "has_pressure_change": True,
            "has_holdup": False,
            "flow_type_side_2": "counter-current",
            "flow_type_side_3": "counter-current",
        }
    )
    return m
Esempio n. 21
0
    def build(self):
        super().build()
        config = self.config  # sorter ref to config for less line splitting

        # All feedwater heaters have a condensing section
        _set_prop_pack(config.condense, config)
        self.condense = FWHCondensing0D(default=config.condense)

        # Add a mixer to add the drain stream from another feedwater heater
        if config.has_drain_mixer:
            mix_cfg = {  # general unit model config
                "dynamic": config.dynamic,
                "has_holdup": config.has_holdup,
                "property_package": config.property_package,
                "property_package_args": config.property_package_args,
                "momentum_mixing_type": MomentumMixingType.none,
                "material_balance_type": MaterialBalanceType.componentTotal,
                "inlet_list": ["steam", "drain"],
            }
            self.drain_mix = Mixer(default=mix_cfg)

            @self.drain_mix.Constraint(self.drain_mix.flowsheet().config.time)
            def mixer_pressure_constraint(b, t):
                """
                Constraint to set the drain mixer pressure to the pressure of
                the steam extracted from the turbine. The drain inlet should
                always be a higher pressure than the steam inlet.
                """
                return b.steam_state[t].pressure == b.mixed_state[t].pressure

            # Connect the mixer to the condensing section inlet
            self.mix_out_arc = Arc(source=self.drain_mix.outlet,
                                   destination=self.condense.inlet_1)

        # Add a desuperheat section before the condensing section
        if config.has_desuperheat:
            _set_prop_pack(config.desuperheat, config)
            self.desuperheat = HeatExchanger(default=config.desuperheat)
            # set default area less than condensing section area, this will
            # almost always be overridden by the user fixing an area later
            self.desuperheat.area.value = 10
            if config.has_drain_mixer:
                self.desuperheat_drain_arc = Arc(
                    source=self.desuperheat.outlet_1,
                    destination=self.drain_mix.steam)
            else:
                self.desuperheat_drain_arc = Arc(
                    source=self.desuperheat.outlet_1,
                    destination=self.condense.inlet_1)
            self.condense_out2_arc = Arc(source=self.condense.outlet_2,
                                         destination=self.desuperheat.inlet_2)

        # Add a drain cooling section after the condensing section
        if config.has_drain_cooling:
            _set_prop_pack(config.cooling, config)
            self.cooling = HeatExchanger(default=config.cooling)
            # set default area less than condensing section area, this will
            # almost always be overridden by the user fixing an area later
            self.cooling.area.value = 10
            self.cooling_out2_arc = Arc(source=self.cooling.outlet_2,
                                        destination=self.condense.inlet_2)
            self.condense_out1_arc = Arc(source=self.condense.outlet_1,
                                         destination=self.cooling.inlet_1)

        TransformationFactory("network.expand_arcs").apply_to(self)
Esempio n. 22
0
def test_serialize_flowsheet():
    # Construct the model from idaes/examples/workshops/Module_2_Flowsheet/Module_2_Flowsheet_Solution.ipynb
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.thermo_params = thermo_props.HDAParameterBlock()
    m.fs.reaction_params = reaction_props.HDAReactionParameterBlock(
        default={"property_package": m.fs.thermo_params})

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

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

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

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

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

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

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

    assert len(difference) == 0

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

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

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

    assert named_edges_results == named_edges_truth
Esempio n. 23
0
def build(number_of_stages=2):
    # ---building model---
    m = ConcreteModel()

    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.properties = props.NaClParameterBlock()
    m.fs.costing = WaterTAPCosting()

    m.fs.NumberOfStages = Param(initialize=number_of_stages)
    m.fs.StageSet = RangeSet(m.fs.NumberOfStages)
    m.fs.LSRRO_StageSet = RangeSet(2, m.fs.NumberOfStages)
    m.fs.NonFinal_StageSet = RangeSet(m.fs.NumberOfStages-1)

    m.fs.feed = Feed(default={'property_package': m.fs.properties})
    m.fs.product = Product(default={'property_package': m.fs.properties})
    m.fs.disposal = Product(default={'property_package': m.fs.properties})

    # Add the mixers
    m.fs.Mixers = Mixer(m.fs.NonFinal_StageSet, default={
            "property_package": m.fs.properties,
            "momentum_mixing_type": MomentumMixingType.equality,  # booster pump will match pressure
            "inlet_list": ['upstream', 'downstream']})

    total_pump_work = 0
    # Add the pumps
    m.fs.PrimaryPumps = Pump(m.fs.StageSet, default={"property_package": m.fs.properties})
    for pump in m.fs.PrimaryPumps.values():
        pump.costing = UnitModelCostingBlock(default={
                "flowsheet_costing_block":m.fs.costing})
        m.fs.costing.cost_flow(pyunits.convert(pump.work_mechanical[0], to_units=pyunits.kW), "electricity")

    # Add the equalizer pumps
    m.fs.BoosterPumps = Pump(m.fs.LSRRO_StageSet, default={"property_package": m.fs.properties})
    for pump in m.fs.BoosterPumps.values():
        pump.costing = UnitModelCostingBlock(default={
                "flowsheet_costing_block":m.fs.costing})
        m.fs.costing.cost_flow(pyunits.convert(pump.work_mechanical[0], to_units=pyunits.kW), "electricity")

    # Add the stages ROs
    m.fs.ROUnits = ReverseOsmosis0D(m.fs.StageSet, 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})
    for ro_unit in m.fs.ROUnits.values():
        ro_unit.costing = UnitModelCostingBlock(default={
                "flowsheet_costing_block":m.fs.costing})

    # Add EnergyRecoveryDevice
    m.fs.EnergyRecoveryDevice = Pump(default={"property_package": m.fs.properties})
    m.fs.EnergyRecoveryDevice.costing = UnitModelCostingBlock(default={
            "flowsheet_costing_block":m.fs.costing,
            "costing_method_arguments":{"pump_type":PumpType.energy_recovery_device}})
    m.fs.costing.cost_flow(pyunits.convert(m.fs.EnergyRecoveryDevice.work_mechanical[0], to_units=pyunits.kW), "electricity")

    # additional variables or expressions
    # system water recovery
    m.fs.water_recovery = Var(
            initialize=0.5,
            bounds=(0, 1),
            domain=NonNegativeReals,
            units=pyunits.dimensionless,
            doc='System Water Recovery')
    m.fs.eq_water_recovery = Constraint(expr=\
              sum(m.fs.feed.flow_mass_phase_comp[0,'Liq',:]) * m.fs.water_recovery == \
              sum(m.fs.product.flow_mass_phase_comp[0,'Liq',:]) )

    # costing
    m.fs.costing.cost_process()
    product_flow_vol_total = m.fs.product.properties[0].flow_vol
    m.fs.costing.add_LCOW(product_flow_vol_total)
    m.fs.costing.add_specific_energy_consumption(product_flow_vol_total)

    # objective
    m.fs.objective = Objective(expr=m.fs.costing.LCOW)

    # connections

    # Connect the feed to the first pump
    m.fs.feed_to_pump = Arc(source=m.fs.feed.outlet, destination=m.fs.PrimaryPumps[1].inlet)

    # Connect the primary RO permeate to the product
    m.fs.primary_RO_to_product = Arc(source=m.fs.ROUnits[1].permeate, destination=m.fs.product.inlet)

    # Connect the Pump n to the Mixer n
    m.fs.pump_to_mixer = Arc(m.fs.NonFinal_StageSet,
            rule=lambda fs,n : {'source':fs.PrimaryPumps[n].outlet,
                                'destination':fs.Mixers[n].upstream})

    # Connect the Mixer n to the Stage n
    m.fs.mixer_to_stage = Arc(m.fs.NonFinal_StageSet,
            rule=lambda fs,n : {'source':fs.Mixers[n].outlet,
                                'destination':fs.ROUnits[n].inlet})

    # Connect the Stage n to the Pump n+1
    m.fs.stage_to_pump = Arc(m.fs.NonFinal_StageSet,
            rule=lambda fs,n : {'source':fs.ROUnits[n].retentate,
                                'destination':fs.PrimaryPumps[n+1].inlet})

    # Connect the Stage n to the Eq Pump n
    m.fs.stage_to_eq_pump = Arc(m.fs.LSRRO_StageSet,
            rule=lambda fs,n : {'source':fs.ROUnits[n].permeate,
                                'destination':fs.BoosterPumps[n].inlet})

    # Connect the Eq Pump n to the Mixer n-1
    m.fs.eq_pump_to_mixer = Arc(m.fs.LSRRO_StageSet,
            rule=lambda fs,n : {'source':fs.BoosterPumps[n].outlet,
                                'destination':fs.Mixers[n-1].downstream})

    # Connect the Pump N to the Stage N
    last_stage = m.fs.StageSet.last()
    m.fs.pump_to_stage = Arc(source=m.fs.PrimaryPumps[last_stage].outlet,
            destination=m.fs.ROUnits[last_stage].inlet)

    # Connect Final Stage to EnergyRecoveryDevice Pump
    m.fs.stage_to_erd = Arc(source=m.fs.ROUnits[last_stage].retentate,
            destination=m.fs.EnergyRecoveryDevice.inlet)

    # Connect the EnergyRecoveryDevice to the disposal
    m.fs.erd_to_disposal = Arc(source=m.fs.EnergyRecoveryDevice.outlet,
            destination=m.fs.disposal.inlet)

    # additional bounding
    for b in m.component_data_objects(Block, descend_into=True):
        # NaCl solubility limit
        if hasattr(b, 'mass_frac_phase_comp'):
            b.mass_frac_phase_comp['Liq', 'NaCl'].setub(0.26)

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

    return m
Esempio n. 24
0
def build():
    # flowsheet set up
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={'dynamic': False})
    m.fs.properties = props.NaClParameterBlock()
    financials.add_costing_param_block(m.fs)

    # 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
    })
    m.fs.product = Product(default={'property_package': m.fs.properties})
    m.fs.disposal = Product(default={'property_package': m.fs.properties})

    # additional variables or expressions
    feed_flow_vol_total = m.fs.feed.properties[0].flow_vol
    product_flow_vol_total = m.fs.product.properties[0].flow_vol
    m.fs.recovery = Expression(expr=product_flow_vol_total /
                               feed_flow_vol_total)
    m.fs.annual_water_production = Expression(expr=pyunits.convert(
        product_flow_vol_total, to_units=pyunits.m**3 / pyunits.year) *
                                              m.fs.costing_param.load_factor)
    pump_power_total = m.fs.P1.work_mechanical[0] + m.fs.P2.work_mechanical[0]
    m.fs.specific_energy_consumption = Expression(
        expr=pyunits.convert(pump_power_total, to_units=pyunits.kW) /
        pyunits.convert(product_flow_vol_total,
                        to_units=pyunits.m**3 / pyunits.hr))

    # costing
    m.fs.P1.get_costing(module=financials, pump_type="High pressure")
    m.fs.P2.get_costing(module=financials, pump_type="High pressure")
    m.fs.RO.get_costing(module=financials)
    m.fs.PXR.get_costing(module=financials)
    financials.get_system_costing(m.fs)

    # 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
    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'))
    iscale.calculate_scaling_factors(m)

    return m