Пример #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_ZONF(m, base='ion'):
    """
    Builds a ZONF model based on specified rejection and solvent flux.
    Requires prop_ion property package.
    """

    if base not in ['ion']:
        raise ValueError('Unexpected property base {base} for build_ZONF'
                         ''.format(base=base))
    prop = property_models.get_prop(m, base=base)

    m.fs.NF = NanofiltrationZO(default={
        "property_package": prop,
        "has_pressure_change": False
    })

    # specify
    m.fs.NF.flux_vol_solvent.fix(1.67e-6)
    m.fs.NF.area.fix(500)
    m.fs.NF.properties_permeate[0].pressure.fix(101325)

    m.fs.NF.rejection_phase_comp[0, 'Liq', 'Na'].fix(0.01)
    m.fs.NF.rejection_phase_comp[0, 'Liq', 'Ca'].fix(0.79)
    m.fs.NF.rejection_phase_comp[0, 'Liq', 'Mg'].fix(0.94)
    m.fs.NF.rejection_phase_comp[0, 'Liq', 'SO4'].fix(0.87)
    m.fs.NF.rejection_phase_comp[
        0, 'Liq', 'Cl'] = 0.15  # guess, but electroneutrality enforced below
    charge_comp = {'Na': 1, 'Ca': 2, 'Mg': 2, 'SO4': -2, 'Cl': -1}
    m.fs.NF.eq_electroneutrality = Constraint(expr=0 == sum(
        charge_comp[j] *
        m.fs.NF.feed_side.properties_out[0].conc_mol_phase_comp['Liq', j]
        for j in charge_comp))
    constraint_scaling_transform(m.fs.NF.eq_electroneutrality, 1)
Пример #3
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)
Пример #4
0
def build_RO(m, base="TDS", level="simple", name_str="RO"):
    """
    Builds a 1DRO model at a specified level (simple or detailed).
    Requires prop_TDS property package.
    """
    if base not in ["TDS"]:
        raise ValueError("Unexpected property base {base} for build_RO"
                         "".format(base=base))
    prop = property_models.get_prop(m, base=base)

    if level == "simple":
        raise ValueError("Unexpected RO level {level} for build_RO"
                         "".format(level=level))

    elif level == "detailed":
        # build unit
        setattr(
            m.fs,
            name_str,
            ReverseOsmosis1D(
                default={
                    "property_package":
                    prop,
                    "has_pressure_change":
                    True,
                    "pressure_change_type":
                    PressureChangeType.calculated,
                    "mass_transfer_coefficient":
                    MassTransferCoefficient.calculated,
                    "concentration_polarization_type":
                    ConcentrationPolarizationType.calculated,
                    "transformation_scheme":
                    "BACKWARD",
                    "transformation_method":
                    "dae.finite_difference",
                    "finite_elements":
                    10,
                }),
        )
        blk = getattr(m.fs, name_str)

        # specify unit
        blk.area.fix(50)
        blk.A_comp.fix(4.2e-12)
        blk.B_comp.fix(3.5e-8)
        blk.mixed_permeate[0].pressure.fix(101325)
        blk.channel_height.fix(1e-3)
        blk.spacer_porosity.fix(0.97)
        blk.N_Re[0, 0].fix(500)

    else:
        raise ValueError("Unexpected argument {level} for level in build_RO"
                         "".format(level=level))
Пример #5
0
def build_feed(m, base='TDS'):
    """
    Build a feed block for a specified property base. The state vars are fixed to the standard condition.
    Property bases include: 'TDS', 'ion', 'salt'
    """

    # feed block
    prop = property_models.get_prop(m, base=base)

    # build
    m.fs.feed = Feed(default={'property_package': prop})

    # specify
    property_models.specify_feed(m.fs.feed.properties[0], base=base)
    m.fs.feed.properties[0].mass_frac_phase_comp  # touch so the block can be initialized
Пример #6
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 = Pump(default={"property_package": prop})
        m.fs.ERD.actual_work.deactivate(
        )  # this constraint is for pumps, not turbines
        m.fs.ERD.turbine_work = Constraint(
            expr=m.fs.ERD.work_fluid[0] *
            m.fs.ERD.efficiency_pump[0] == m.fs.ERD.control_volume.work[0])

    # 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
Пример #7
0
def build_RO(m, base='TDS', level='simple', name_str='RO'):
    """
    Builds a 0DRO model at a specified level (simple or detailed).
    Requires prop_TDS property package.
    """
    if base not in ['TDS']:
        raise ValueError('Unexpected property base {base} for build_RO'
                         ''.format(base=base))
    prop = property_models.get_prop(m, base=base)

    if level == 'simple':
        # build unit
        setattr(
            m.fs, name_str,
            ReverseOsmosis0D(
                default={
                    "property_package":
                    prop,
                    "mass_transfer_coefficient":
                    MassTransferCoefficient.none,
                    "concentration_polarization_type":
                    ConcentrationPolarizationType.none
                }))
        blk = getattr(m.fs, name_str)

        # specify unit
        blk.area.fix(50)
        blk.A_comp.fix(4.2e-12)
        blk.B_comp.fix(3.5e-8)
        blk.permeate.pressure[0].fix(101325)

    elif level == 'detailed':
        # build unit
        setattr(
            m.fs, name_str,
            ReverseOsmosis0D(
                default={
                    "property_package":
                    prop,
                    "has_pressure_change":
                    True,
                    "pressure_change_type":
                    PressureChangeType.calculated,
                    "mass_transfer_coefficient":
                    MassTransferCoefficient.calculated,
                    "concentration_polarization_type":
                    ConcentrationPolarizationType.calculated
                }))
        blk = getattr(m.fs, name_str)

        # specify unit
        blk.area.fix(50)
        blk.A_comp.fix(4.2e-12)
        blk.B_comp.fix(3.5e-8)
        blk.permeate.pressure[0].fix(101325)
        blk.channel_height.fix(1e-3)
        blk.spacer_porosity.fix(0.97)
        blk.N_Re[0, 0].fix(500)

    else:
        raise ValueError('Unexpected argument {level} for level in build_RO'
                         ''.format(level=level))
Пример #8
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
Пример #9
0
def build_tb(m, base_inlet='ion', base_outlet='TDS', name_str=None):
    """
    Build a translator block to convert for the specified base from inlet to outlet.
    """

    if name_str is None:
        name_str = 'tb_' + base_inlet + '_to_' + base_outlet

    if base_inlet not in ['ion', 'salt']:
        raise ValueError(
            'Unexpected property base inlet {base_inlet} for build_tb'
            ''.format(base_inlet=base_inlet))
    prop_inlet = property_models.get_prop(m, base=base_inlet)

    if base_outlet not in ['TDS']:
        raise ValueError(
            'Unexpected property base outlet {base_outlet} for build_tb'
            ''.format(base_outlet=base_outlet))
    prop_outlet = property_models.get_prop(m, base=base_outlet)

    # build translator block
    setattr(
        m.fs, name_str,
        Translator(
            default={
                "inlet_property_package": prop_inlet,
                "outlet_property_package": prop_outlet
            }))
    blk = getattr(m.fs, name_str)

    # scale translator block to get scaling factors
    calculate_scaling_factors(blk)

    # add translator block constraints
    blk.eq_equal_temperature = Constraint(
        expr=blk.inlet.temperature[0] == blk.outlet.temperature[0])
    constraint_scaling_transform(
        blk.eq_equal_temperature,
        get_scaling_factor(blk.properties_in[0].temperature))
    blk.eq_equal_pressure = Constraint(
        expr=blk.inlet.pressure[0] == blk.outlet.pressure[0])
    constraint_scaling_transform(
        blk.eq_equal_pressure,
        get_scaling_factor(blk.properties_in[0].pressure))

    if base_inlet == 'ion' and base_outlet == 'TDS':
        blk.eq_H2O_balance = Constraint(expr=blk.inlet.flow_mass_phase_comp[
            0, 'Liq', 'H2O'] == blk.outlet.flow_mass_phase_comp[0, 'Liq',
                                                                'H2O'])
        constraint_scaling_transform(
            blk.eq_H2O_balance,
            get_scaling_factor(
                blk.properties_out[0].flow_mass_phase_comp['Liq', 'H2O']))

        blk.eq_TDS_balance = Constraint(expr=sum(
            blk.inlet.flow_mass_phase_comp[0, 'Liq', j]
            for j in ['Na', 'Ca', 'Mg', 'SO4', 'Cl'
                      ]) == blk.outlet.flow_mass_phase_comp[0, 'Liq', 'TDS'])
        constraint_scaling_transform(
            blk.eq_TDS_balance,
            get_scaling_factor(
                blk.properties_out[0].flow_mass_phase_comp['Liq', 'TDS']))

    elif base_inlet == 'salt' and base_outlet == 'TDS':
        blk.eq_H2O_balance = Constraint(expr=blk.inlet.flow_mass_phase_comp[
            0, 'Liq', 'H2O'] == blk.outlet.flow_mass_phase_comp[0, 'Liq',
                                                                'H2O'])
        constraint_scaling_transform(
            blk.eq_H2O_balance,
            get_scaling_factor(
                blk.properties_out[0].flow_mass_phase_comp['Liq', 'H2O']))

        blk.eq_TDS_balance = Constraint(expr=sum(
            blk.inlet.flow_mass_phase_comp[0, 'Liq', j]
            for j in ['NaCl', 'CaSO4', 'MgSO4', 'MgCl2'
                      ]) == blk.outlet.flow_mass_phase_comp[0, 'Liq', 'TDS'])
        constraint_scaling_transform(
            blk.eq_TDS_balance,
            get_scaling_factor(
                blk.properties_out[0].flow_mass_phase_comp['Liq', 'TDS']))

    else:
        raise ValueError('Unexpected property base combination for build_tb')

    blk.properties_in[0].mass_frac_phase_comp  # touch for initialization
    blk.properties_out[0].mass_frac_phase_comp
Пример #10
0
def build_tb(m, base_inlet="ion", base_outlet="TDS", name_str=None):
    """
    Build a translator block to convert for the specified base from inlet to outlet.
    """

    if name_str is None:
        name_str = "tb_" + base_inlet + "_to_" + base_outlet

    if base_inlet not in ["ion", "salt"]:
        raise ValueError(
            "Unexpected property base inlet {base_inlet} for build_tb"
            "".format(base_inlet=base_inlet))
    prop_inlet = property_models.get_prop(m, base=base_inlet)

    if base_outlet not in ["TDS"]:
        raise ValueError(
            "Unexpected property base outlet {base_outlet} for build_tb"
            "".format(base_outlet=base_outlet))
    prop_outlet = property_models.get_prop(m, base=base_outlet)

    # build translator block
    setattr(
        m.fs,
        name_str,
        Translator(
            default={
                "inlet_property_package": prop_inlet,
                "outlet_property_package": prop_outlet,
            }),
    )
    blk = getattr(m.fs, name_str)

    # scale translator block to get scaling factors
    calculate_scaling_factors(blk)

    # add translator block constraints
    blk.eq_equal_temperature = Constraint(
        expr=blk.inlet.temperature[0] == blk.outlet.temperature[0])
    constraint_scaling_transform(
        blk.eq_equal_temperature,
        get_scaling_factor(blk.properties_in[0].temperature))
    blk.eq_equal_pressure = Constraint(
        expr=blk.inlet.pressure[0] == blk.outlet.pressure[0])
    constraint_scaling_transform(
        blk.eq_equal_pressure,
        get_scaling_factor(blk.properties_in[0].pressure))

    if base_inlet == "ion" and base_outlet == "TDS":
        blk.eq_H2O_balance = Constraint(expr=blk.inlet.flow_mass_phase_comp[
            0, "Liq", "H2O"] == blk.outlet.flow_mass_phase_comp[0, "Liq",
                                                                "H2O"])
        constraint_scaling_transform(
            blk.eq_H2O_balance,
            get_scaling_factor(
                blk.properties_out[0].flow_mass_phase_comp["Liq", "H2O"]),
        )

        blk.eq_TDS_balance = Constraint(expr=sum(
            blk.inlet.flow_mass_phase_comp[0, "Liq", j]
            for j in ["Na", "Ca", "Mg", "SO4", "Cl"
                      ]) == blk.outlet.flow_mass_phase_comp[0, "Liq", "TDS"])
        constraint_scaling_transform(
            blk.eq_TDS_balance,
            get_scaling_factor(
                blk.properties_out[0].flow_mass_phase_comp["Liq", "TDS"]),
        )

    elif base_inlet == "salt" and base_outlet == "TDS":
        blk.eq_H2O_balance = Constraint(expr=blk.inlet.flow_mass_phase_comp[
            0, "Liq", "H2O"] == blk.outlet.flow_mass_phase_comp[0, "Liq",
                                                                "H2O"])
        constraint_scaling_transform(
            blk.eq_H2O_balance,
            get_scaling_factor(
                blk.properties_out[0].flow_mass_phase_comp["Liq", "H2O"]),
        )

        blk.eq_TDS_balance = Constraint(expr=sum(
            blk.inlet.flow_mass_phase_comp[0, "Liq", j]
            for j in ["NaCl", "CaSO4", "MgSO4", "MgCl2"
                      ]) == blk.outlet.flow_mass_phase_comp[0, "Liq", "TDS"])
        constraint_scaling_transform(
            blk.eq_TDS_balance,
            get_scaling_factor(
                blk.properties_out[0].flow_mass_phase_comp["Liq", "TDS"]),
        )

    else:
        raise ValueError("Unexpected property base combination for build_tb")

    blk.properties_in[0].mass_frac_phase_comp  # touch for initialization
    blk.properties_out[0].mass_frac_phase_comp