Пример #1
0
def build_SepRO(m, base="TDS"):
    """
    Builds RO model based on the IDAES separator.
    Requires prop_TDS property package.
    """
    prop = property_models.get_prop(m, base=base)

    m.fs.RO = Separator(
        default={
            "property_package": prop,
            "outlet_list": ["retentate", "permeate"],
            "split_basis": SplittingType.componentFlow,
            "energy_split_basis": EnergySplittingType.equal_temperature,
        }
    )

    # specify
    if base == "TDS":
        m.fs.RO.split_fraction[0, "permeate", "H2O"].fix(0.5)
        m.fs.RO.split_fraction[0, "permeate", "TDS"].fix(0.01)
    else:
        raise ValueError(
            "Unexpected property base {base} provided to build_SepRO"
            "".format(base=base)
        )

    # scale
    set_scaling_factor(
        m.fs.RO.split_fraction, 1
    )  # TODO: IDAES should set these scaling factors by default
    constraint_scaling_transform(m.fs.RO.sum_split_frac[0.0, "H2O"], 1)
    constraint_scaling_transform(m.fs.RO.sum_split_frac[0.0, "TDS"], 1)
Пример #2
0
def build_SepNF(m, base="ion"):
    """
    Builds NF model based on the IDAES separator for a specified property base.
    Requires prop_ion or prop_salt property package.
    """
    prop = property_models.get_prop(m, base=base)

    m.fs.NF = Separator(
        default={
            "property_package": prop,
            "outlet_list": ["retentate", "permeate"],
            "split_basis": SplittingType.componentFlow,
            "energy_split_basis": EnergySplittingType.equal_temperature,
        }
    )

    # specify
    if base == "ion":
        m.fs.NF.split_fraction[0, "permeate", "H2O"].fix(0.9)
        m.fs.NF.split_fraction[0, "permeate", "Na"].fix(0.9)
        m.fs.NF.split_fraction[0, "permeate", "Ca"].fix(0.1)
        m.fs.NF.split_fraction[0, "permeate", "Mg"].fix(0.1)
        m.fs.NF.split_fraction[0, "permeate", "SO4"].fix(0.1)
        # Cl split fraction determined through electro-neutrality for the retentate
        charge_dict = {"Na": 1, "Ca": 2, "Mg": 2, "SO4": -2, "Cl": -1}
        m.fs.NF.EN_out = Constraint(
            expr=0
            == sum(
                charge_dict[j]
                * m.fs.NF.retentate_state[0].flow_mol_phase_comp["Liq", j]
                for j in charge_dict
            )
        )
        constraint_scaling_transform(m.fs.NF.EN_out, 1)
    elif base == "salt":
        m.fs.NF.split_fraction[0, "permeate", "H2O"].fix(0.9)
        m.fs.NF.split_fraction[0, "permeate", "NaCl"].fix(0.9)
        m.fs.NF.split_fraction[0, "permeate", "CaSO4"].fix(0.1)
        m.fs.NF.split_fraction[0, "permeate", "MgSO4"].fix(0.1)
        m.fs.NF.split_fraction[0, "permeate", "MgCl2"].fix(0.2)

    # scale
    set_scaling_factor(
        m.fs.NF.split_fraction, 1
    )  # TODO: IDAES should set these scaling factors by default
    if base == "ion":
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "H2O"], 1)
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "Na"], 1)
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "Ca"], 1)
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "Mg"], 1)
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "SO4"], 1)
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "Cl"], 1)
    elif base == "salt":
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "H2O"], 1)
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "NaCl"], 1)
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "CaSO4"], 1)
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "MgSO4"], 1)
        constraint_scaling_transform(m.fs.NF.sum_split_frac[0.0, "MgCl2"], 1)
Пример #3
0
def declare_heatx_unit(h):
    """
    Declare all units of the heat exchanger network.
    Optional keyword arguments:
    ---------------------------
    :param h: the heat exchanger network
    :return: return the heat exchanger network
    """
    h.fs = FlowsheetBlock(default={"dynamic": False})
    h.fs.thermo_params = thermo_props.MethaneParameterBlock()
    h.fs.Split2 = Separator(default={
        "dynamic": False,
        "num_outlets": 3,
        "property_package": h.fs.thermo_params
    })
    h.fs.HX1a = HeatExchanger(
        default={
            "dynamic": False,
            "delta_temperature_callback": delta_temperature_amtd_callback,
            "shell": {
                "property_package": h.fs.thermo_params
            },
            "tube": {
                "property_package": h.fs.thermo_params
            }
        })
    h.fs.HX2a = HeatExchanger(
        default={
            "dynamic": False,
            "delta_temperature_callback": delta_temperature_amtd_callback,
            "shell": {
                "property_package": h.fs.thermo_params
            },
            "tube": {
                "property_package": h.fs.thermo_params
            }
        })
    h.fs.HX2b = HeatExchanger(
        default={
            "dynamic": False,
            "delta_temperature_callback": delta_temperature_amtd_callback,
            "shell": {
                "property_package": h.fs.thermo_params
            },
            "tube": {
                "property_package": h.fs.thermo_params
            }
        })
    h.fs.HX1b = Heater(default={"property_package": h.fs.thermo_params})

    return h
Пример #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
Пример #5
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
Пример #6
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 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
Пример #8
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)
Пример #9
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)
Пример #10
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