Пример #1
0
    def build(self):
        """
        Begin building model (pre-DAE transformation).
        Args:
            None
        Returns:
            None
        """

        # Call UnitModel.build to setup dynamics
        super(DrumData, self).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,
            has_rate_reactions=False,
            has_equilibrium_reactions=False)

        self.control_volume.add_energy_balances(
            balance_type=self.config.energy_balance_type,
            has_heat_of_reaction=False,
            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)

        # Add Ports
        self.add_inlet_port()
        self.add_outlet_port()

        # Add object references
        add_object_reference(self, "volume", 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):
            add_object_reference(self, "heat_duty", self.control_volume.heat)

        if (self.config.has_pressure_change is True
                and self.config.momentum_balance_type != 'none'):
            add_object_reference(self, "deltaP", self.control_volume.deltaP)

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

        # Construct performance equations
        self._make_performance()
def test_user_set_scaling():
    m = pyo.ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.cv = ControlVolume0DBlock(default={"property_package": m.fs.pp})
    m.fs.cv.add_geometry()
    m.fs.cv.add_state_blocks(has_phase_equilibrium=False)
    m.fs.cv.add_material_balances(
        balance_type=MaterialBalanceType.componentTotal,
        has_phase_equilibrium=False)
    m.fs.cv.add_energy_balances(
        balance_type=EnergyBalanceType.enthalpyTotal,
        has_heat_transfer=True,
        has_work_transfer=True)
    # add momentum balance
    m.fs.cv.add_momentum_balances(
        balance_type=MomentumBalanceType.pressureTotal,
        has_pressure_change=True)

    # The scaling factors used for this test were selected to be easy values to
    # test, they do not represent typical scaling factors.
    iscale.set_scaling_factor(m.fs.cv.heat, 11)
    iscale.set_scaling_factor(m.fs.cv.work, 12)
    iscale.calculate_scaling_factors(m)
    # Make sure the heat and work scaling factors are set and not overwritten
    # by the defaults in calculate_scaling_factors
    assert iscale.get_scaling_factor(m.fs.cv.heat) == 11
    assert iscale.get_scaling_factor(m.fs.cv.work) == 12
def test_full_auto_scaling_mbtype_element():
    m = pyo.ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": True, "time_units": pyo.units.s})
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.rp = ReactionParameterTestBlock(default={"property_package": m.fs.pp})
    m.fs.cv = ControlVolume0DBlock(default={
            "property_package": m.fs.pp,
            "reaction_package": m.fs.rp,
            "dynamic": True})
    m.fs.cv.add_geometry()
    m.fs.cv.add_state_blocks(has_phase_equilibrium=False)
    m.fs.cv.add_reaction_blocks(has_equilibrium=False)

    m.fs.cv.add_total_element_balances(has_mass_transfer=True)

    m.discretizer = pyo.TransformationFactory('dae.finite_difference')
    m.discretizer.apply_to(m, nfe=3, wrt=m.fs.time, scheme="BACKWARD")

    iscale.calculate_scaling_factors(m)

    # check that all variables have scaling factors
    unscaled_var_list = list(iscale.unscaled_variables_generator(m))
    assert len(unscaled_var_list) == 0
    # check that all constraints have been scaled
    unscaled_constraint_list = list(iscale.unscaled_constraints_generator(m))
    assert len(unscaled_constraint_list) == 0
Пример #4
0
def test_add_outlet_port_CV0D_full_args():
    m = ConcreteModel()
    m.fs = Flowsheet()
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.u = Unit()
    m.fs.u._setup_dynamics()

    m.fs.u.cv = ControlVolume0DBlock(default={"property_package": m.fs.pp})

    m.fs.u.cv.add_state_blocks(has_phase_equilibrium=False)

    p_obj = m.fs.u.add_outlet_port(name="test_port",
                                   block=m.fs.u.cv,
                                   doc="Test")

    assert isinstance(p_obj, Port)
    assert hasattr(m.fs.u, "test_port")
    assert len(m.fs.u.test_port) == 1

    # Set new outlet conditions to differentiate from inlet
    m.fs.u.cv.properties_out[0].a = 10
    m.fs.u.cv.properties_out[0].b = 20
    m.fs.u.cv.properties_out[0].c = 30

    for p in m.fs.pp.phase_list:
        for j in m.fs.pp.component_list:
            assert m.fs.u.test_port.component_flow_phase[0, p, j].value == (
                m.fs.u.cv.properties_out[0].flow_mol_phase_comp[p, j].value)
    assert m.fs.u.test_port.pressure[0].value == \
        m.fs.u.cv.properties_out[0].pressure.value
    assert m.fs.u.test_port.temperature[0].value == \
        m.fs.u.cv.properties_out[0].temperature.value
Пример #5
0
def _make_heater_control_volume(o, name, config,
                                dynamic=None, has_holdup=None):
    """
    This is seperated from the main heater class so it can be reused to create
    control volumes for different types of heat exchange models.
    """
    if dynamic is None:
        dynamic = config.dynamic
    if has_holdup is None:
        has_holdup = config.has_holdup
    # we have to attach this control volume to the model for the rest of
    # the steps to work
    o.add_component(name, ControlVolume0DBlock(default={
        "dynamic": dynamic,
        "has_holdup": has_holdup,
        "property_package": config.property_package,
        "property_package_args": config.property_package_args}))
    control_volume = getattr(o, name)
    # Add inlet and outlet state blocks to control volume
    control_volume.add_state_blocks(
        has_phase_equilibrium=config.has_phase_equilibrium)
    # Add material balance
    control_volume.add_material_balances(
        balance_type=config.material_balance_type,
        has_phase_equilibrium=config.has_phase_equilibrium)
    # add energy balance
    control_volume.add_energy_balances(
        balance_type=config.energy_balance_type,
        has_heat_transfer=True)
    # add momentum balance
    control_volume.add_momentum_balances(
        balance_type=config.momentum_balance_type,
        has_pressure_change=config.has_pressure_change)
    return control_volume
Пример #6
0
def test_get_stream_table_contents_CV0D():
    m = ConcreteModel()
    m.fs = Flowsheet()
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.u = Unit()
    m.fs.u._setup_dynamics()

    m.fs.u.control_volume = ControlVolume0DBlock(
        default={"property_package": m.fs.pp})

    m.fs.u.control_volume.add_state_blocks(has_phase_equilibrium=False)

    m.fs.u.add_inlet_port()
    m.fs.u.add_outlet_port()

    df = m.fs.u._get_stream_table_contents()

    assert df.loc["component_flow_phase ('p1', 'c1')"]["Inlet"] == 2
    assert df.loc["component_flow_phase ('p1', 'c2')"]["Inlet"] == 2
    assert df.loc["component_flow_phase ('p2', 'c1')"]["Inlet"] == 2
    assert df.loc["component_flow_phase ('p2', 'c2')"]["Inlet"] == 2
    assert df.loc["pressure"]["Inlet"] == 1e5
    assert df.loc["temperature"]["Inlet"] == 300

    assert df.loc["component_flow_phase ('p1', 'c1')"]["Outlet"] == 2
    assert df.loc["component_flow_phase ('p1', 'c2')"]["Outlet"] == 2
    assert df.loc["component_flow_phase ('p2', 'c1')"]["Outlet"] == 2
    assert df.loc["component_flow_phase ('p2', 'c2')"]["Outlet"] == 2
    assert df.loc["pressure"]["Outlet"] == 1e5
    assert df.loc["temperature"]["Outlet"] == 300
Пример #7
0
def test_add_outlet_port_CV0D_full_args():
    m = ConcreteModel()
    m.fs = Flowsheet()
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.u = Unit()
    m.fs.u._setup_dynamics()

    m.fs.u.cv = ControlVolume0DBlock(default={"property_package": m.fs.pp})

    m.fs.u.cv.add_state_blocks(has_phase_equilibrium=False)

    p_obj = m.fs.u.add_outlet_port(name="test_port",
                                   block=m.fs.u.cv,
                                   doc="Test")

    assert isinstance(p_obj, Port)
    assert hasattr(m.fs.u, "test_port")
    assert len(m.fs.u.test_port) == 1

    # Set new outlet conditions to differentiate from inlet
    m.fs.u.cv.properties_out[0].a = 10
    m.fs.u.cv.properties_out[0].b = 20
    m.fs.u.cv.properties_out[0].c = 30

    assert m.fs.u.test_port.a[0].value == \
        m.fs.u.cv.properties_out[0].a.value
    assert m.fs.u.test_port.b[0].value == \
        m.fs.u.cv.properties_out[0].b.value
    assert m.fs.u.test_port.c[0].value == \
        m.fs.u.cv.properties_out[0].c.value
Пример #8
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()
        # This model requires the IAPWS95 property package with the mixed phase
        # option, therefore, phase equilibrium calculations are handled by
        # the property package.
        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)

        # Add Ports
        self.add_inlet_port()
        self.add_outlet_port()

        # Add object references
        self.volume = 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 = Reference(self.control_volume.heat)

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

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

        # Construct performance equations
        self._make_performance()
Пример #9
0
def test_CV_integration(model):
    model.fs.cv = ControlVolume0DBlock(
        default={"property_package": model.fs.water_props})

    model.fs.cv.add_geometry()

    model.fs.cv.add_state_blocks(has_phase_equilibrium=True)

    model.fs.cv.add_material_balances(has_phase_equilibrium=True)
Пример #10
0
    def build(self):
        """
        Begin building model.

        Args:
            None

        Returns:
            None
        """
        # Call UnitModel.build to setup dynamics
        super(FeedFlashData, self).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
            })

        # No need for control volume geometry

        self.control_volume.add_state_blocks(has_phase_equilibrium=True)

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

        # Add isothermal constraint
        @self.Constraint(self.flowsheet().config.time,
                         doc="Isothermal constraint")
        def isothermal(b, t):
            return (b.control_volume.properties_in[t].temperature ==
                    b.control_volume.properties_out[t].temperature)

        self.control_volume.add_momentum_balances(
            balance_type=MomentumBalanceType.pressureTotal)

        # Add references to all feed state vars
        s_vars = self.control_volume.properties_in[
            self.flowsheet().config.time.first()].define_state_vars()
        for s in s_vars:
            l_name = s_vars[s].local_name
            if s_vars[s].is_indexed():
                slicer = (self.control_volume.properties_in[:].component(
                    l_name)[...])
            else:
                slicer = self.control_volume.properties_in[:].component(l_name)

            r = Reference(slicer)
            setattr(self, s, r)

        # Add Ports
        self.add_outlet_port()
Пример #11
0
    def build(self):
        """
        Begin building model (pre-DAE transformation).
        Args:
            None
        Returns:
            None
        """
        # Call UnitModel.build to setup dynamics
        super(StoichiometricReactorData, self).build()

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

        self.control_volume.add_state_blocks(has_phase_equilibrium=False)

        self.control_volume.add_reaction_blocks(has_equilibrium=False)

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

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

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

        # Add Ports
        self.add_inlet_port()
        self.add_outlet_port()

        # Add performance equations
        add_object_reference(self, "rate_reaction_idx_ref",
                             self.config.reaction_package.rate_reaction_idx)
        add_object_reference(self, "rate_reaction_extent",
                             self.control_volume.rate_reaction_extent)

        # Set references to balance terms at unit level
        if (self.config.has_heat_transfer is True
                and self.config.energy_balance_type != 'none'):
            add_object_reference(self, "heat_duty", self.control_volume.heat)
        if (self.config.has_pressure_change is True
                and self.config.momentum_balance_type != 'none'):
            add_object_reference(self, "deltaP", self.control_volume.deltaP)
Пример #12
0
def test_add_outlet_port_CV0D_no_default_block():
    m = ConcreteModel()
    m.fs = Flowsheet()
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.u = Unit()
    m.fs.u._setup_dynamics()

    m.fs.u.cv = ControlVolume0DBlock(default={"property_package": m.fs.pp})

    with pytest.raises(ConfigurationError):
        m.fs.u.add_outlet_port()
Пример #13
0
    def build(self):
        super().build()

        # this creates blank scaling factors, which are populated later
        self.scaling_factor = Suffix(direction=Suffix.EXPORT)

        # Next, get the base units of measurement from the property definition
        units_meta = self.config.property_package.get_metadata(
        ).get_derived_units

        # Add control volume
        self.control_volume = ControlVolume0DBlock(
            default={
                "dynamic": False,
                "has_holdup": False,
                "property_package": self.config.property_package,
                "property_package_args": self.config.property_package_args,
            })

        self.control_volume.add_state_blocks(has_phase_equilibrium=False)

        # complete condensation mass balance
        @self.control_volume.Constraint(
            self.flowsheet().time,
            self.config.property_package.component_list,
            doc="Mass balance",
        )
        def mass_balance(b, t, j):
            lb = b.properties_out[t].get_material_flow_terms("Vap", j).lb
            b.properties_out[t].get_material_flow_terms("Vap", j).fix(lb)
            return b.properties_in[t].get_material_flow_terms(
                "Vap", j) + b.properties_in[t].get_material_flow_terms(
                    "Liq", j) == b.properties_out[t].get_material_flow_terms(
                        "Liq", j)

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

        self.control_volume.add_momentum_balances(
            balance_type=self.config.momentum_balance_type)

        # # Add constraints
        @self.Constraint(self.flowsheet().time,
                         doc="Saturation pressure constraint")
        def eq_condenser_pressure_sat(b, t):
            return (b.control_volume.properties_out[t].pressure >=
                    b.control_volume.properties_out[t].pressure_sat)

        # Add ports
        self.add_port(name="inlet", block=self.control_volume.properties_in)
        self.add_port(name="outlet", block=self.control_volume.properties_out)
Пример #14
0
    def test_CV_integration(self, frame):
        frame.fs.cv = ControlVolume0DBlock(
            default={"property_package": frame.fs.params})

        frame.fs.cv.add_geometry()

        frame.fs.cv.add_state_blocks(has_phase_equilibrium=True)

        frame.fs.cv.add_material_balances(has_phase_equilibrium=True)

        frame.fs.cv.add_energy_balances()

        frame.fs.cv.add_momentum_balances()
Пример #15
0
def test_get_stream_table_contents_CV0D_missing_default_port():
    m = ConcreteModel()
    m.fs = Flowsheet()
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.u = Unit()
    m.fs.u._setup_dynamics()

    m.fs.u.control_volume = ControlVolume0DBlock(
        default={"property_package": m.fs.pp})

    m.fs.u.control_volume.add_state_blocks(has_phase_equilibrium=False)

    with pytest.raises(ConfigurationError):
        m.fs.u._get_stream_table_contents()
Пример #16
0
def test_report_CV0D():
    m = ConcreteModel()
    m.fs = Flowsheet()
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.u = Unit()
    m.fs.u._setup_dynamics()

    m.fs.u.control_volume = ControlVolume0DBlock(
        default={"property_package": m.fs.pp})

    m.fs.u.control_volume.add_state_blocks(has_phase_equilibrium=False)

    m.fs.u.add_inlet_port()
    m.fs.u.add_outlet_port()

    m.fs.u.report()
Пример #17
0
def test_add_outlet_port_CV0D_part_args():
    m = ConcreteModel()
    m.fs = Flowsheet()
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.u = Unit()
    m.fs.u._setup_dynamics()

    m.fs.u.cv = ControlVolume0DBlock(default={"property_package": m.fs.pp})

    m.fs.u.cv.add_state_blocks(has_phase_equilibrium=False)

    with pytest.raises(ConfigurationError):
        m.fs.u.add_outlet_port(block=m.fs.u.cv)

    with pytest.raises(ConfigurationError):
        m.fs.u.add_outlet_port(name="foo")
def test_base_build():
    m = pyo.ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.cv = ControlVolume0DBlock(default={
            "property_package": m.fs.pp
        }
    )
    m.fs.cv.add_geometry()
    m.fs.cv.add_state_blocks(has_phase_equilibrium=False)
    m.fs.cv.add_material_balances(
        balance_type=MaterialBalanceType.componentTotal,
        has_phase_equilibrium=False)
    m.fs.cv.add_energy_balances(
        balance_type=EnergyBalanceType.enthalpyTotal,
        has_heat_transfer=True,
        has_work_transfer=True)
    # add momentum balance
    m.fs.cv.add_momentum_balances(
        balance_type=MomentumBalanceType.pressureTotal,
        has_pressure_change=True)

    # The scaling factors used for this test were selected to be easy values to
    # test, they do not represent typical scaling factors.
    iscale.set_scaling_factor(m.fs.cv.heat, 11)
    iscale.set_scaling_factor(m.fs.cv.work, 12)
    iscale.calculate_scaling_factors(m)
    # Make sure the heat and work scaling factors are set and not overwitten
    # by the defaults in calculate_scaling_factors
    assert iscale.get_scaling_factor(m.fs.cv.heat) == 11
    assert iscale.get_scaling_factor(m.fs.cv.work) == 12

    # Didn't specify a deltaP scaling factor, so by default pressure in scaling
    # factor * 10 is used.
    for v in m.fs.cv.deltaP.values(): #deltaP is time indexed
        assert iscale.get_scaling_factor(v) == 1040

    # check scaling on mass, energy, and pressure balances.
    for c in m.fs.cv.material_balances.values():
        # this uses the minmum material flow term scale
        assert iscale.get_constraint_transform_applied_scaling_factor(c) == 112
    for c in m.fs.cv.enthalpy_balances.values():
        # this uses the minmum enthalpy flow term scale
        assert iscale.get_constraint_transform_applied_scaling_factor(c) == 110
    for c in m.fs.cv.pressure_balance.values():
        # This uses the inlet pressure scale
        assert iscale.get_constraint_transform_applied_scaling_factor(c) == 104
def test_full_auto_scaling_mbtype_phase():
    m = pyo.ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": True, "time_units": pyo.units.s})
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.rp = ReactionParameterTestBlock(default={"property_package": m.fs.pp})
    m.fs.cv = ControlVolume0DBlock(default={
            "property_package": m.fs.pp,
            "reaction_package": m.fs.rp,
            "dynamic": True})
    m.fs.cv.add_geometry()
    m.fs.cv.add_state_blocks(has_phase_equilibrium=True)
    m.fs.cv.add_reaction_blocks(has_equilibrium=True)

    m.fs.cv.add_material_balances(
        balance_type=MaterialBalanceType.componentPhase,
        has_rate_reactions=True,
        has_equilibrium_reactions=True,
        has_phase_equilibrium=True,
        has_mass_transfer=True)

    m.fs.cv.add_energy_balances(
        balance_type=EnergyBalanceType.enthalpyTotal,
        has_heat_of_reaction=True,
        has_heat_transfer=True,
        has_work_transfer=True,
        has_enthalpy_transfer=True)

    m.fs.cv.add_momentum_balances(
        balance_type=MomentumBalanceType.pressureTotal,
        has_pressure_change=True)

    m.discretizer = pyo.TransformationFactory('dae.finite_difference')
    m.discretizer.apply_to(m, nfe=3, wrt=m.fs.time, scheme="BACKWARD")

    iscale.calculate_scaling_factors(m)

    # check that all variables have scaling factors
    unscaled_var_list = list(iscale.unscaled_variables_generator(m))
    # Unscaled variables are:
    # rate_reaction_extent (2 reactions, 4 time points)
    # equilibrium_reaction_extent  (2 reactions, 4 time points)
    # phase_equilibrium_generation  (2 reactions, 4 time points)
    assert len(unscaled_var_list) == 24
    # check that all constraints have been scaled
    unscaled_constraint_list = list(iscale.unscaled_constraints_generator(m))
    assert len(unscaled_constraint_list) == 0
def test_full_auto_scaling():
    m = pyo.ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.rp = ReactionParameterTestBlock(default={"property_package": m.fs.pp})
    m.fs.cv = ControlVolume0DBlock(default={
        "property_package": m.fs.pp,
        "reaction_package": m.fs.rp})
    m.fs.cv.add_geometry()
    m.fs.cv.add_state_blocks(has_phase_equilibrium=True)
    m.fs.cv.add_reaction_blocks(has_equilibrium=True)

    m.fs.cv.add_material_balances(
        balance_type=MaterialBalanceType.componentTotal,
        has_rate_reactions=True,
        has_equilibrium_reactions=True,
        has_phase_equilibrium=True,
        has_mass_transfer=True)

    m.fs.cv.add_energy_balances(
        balance_type=EnergyBalanceType.enthalpyTotal,
        has_heat_of_reaction=True,
        has_heat_transfer=True,
        has_work_transfer=True,
        has_enthalpy_transfer=True)

    m.fs.cv.add_momentum_balances(
        balance_type=MomentumBalanceType.pressureTotal,
        has_pressure_change=True)

    iscale.calculate_scaling_factors(m)

    # check that all variables have scaling factors
    unscaled_var_list = list(iscale.unscaled_variables_generator(m))
    # Unscaled variables are:
    # rate_reaction_extent (2 reactions)
    # equilibrium_reaction_extent  (2 reactions)
    assert len(unscaled_var_list) == 4
    # check that all constraints have been scaled
    unscaled_constraint_list = list(iscale.unscaled_constraints_generator(m))
    assert len(unscaled_constraint_list) == 0
def test_basic_scaling():
    m = pyo.ConcreteModel()
    m.fs = FlowsheetBlock(default={"dynamic": False})
    m.fs.pp = PhysicalParameterTestBlock()
    # Set flag to include inherent reactions
    m.fs.pp._has_inherent_reactions = True

    m.fs.cv = ControlVolume0DBlock(default={"property_package": m.fs.pp})

    m.fs.cv.add_geometry()
    m.fs.cv.add_state_blocks(has_phase_equilibrium=False)

    m.fs.cv.add_material_balances(
        balance_type=MaterialBalanceType.componentTotal,
        has_phase_equilibrium=False)

    m.fs.cv.add_energy_balances(
        balance_type=EnergyBalanceType.enthalpyTotal)

    m.fs.cv.add_momentum_balances(
        balance_type=MomentumBalanceType.pressureTotal,
        has_pressure_change=True)

    iscale.calculate_scaling_factors(m)

    # check scaling on select variables
    assert iscale.get_scaling_factor(m.fs.cv.volume[0]) == 1e-2
    assert iscale.get_scaling_factor(
        m.fs.cv.deltaP[0]) == 1040  # 10x the properties pressure scaling factor
    assert iscale.get_scaling_factor(m.fs.cv.properties_in[0].flow_vol) == 100

    # check scaling on mass, energy, and pressure balances.
    for c in m.fs.cv.material_balances.values():
        # this uses the minimum material flow term scale
        assert iscale.get_constraint_transform_applied_scaling_factor(c) == 112
    for c in m.fs.cv.enthalpy_balances.values():
        # this uses the minimum enthalpy flow term scale
        assert iscale.get_constraint_transform_applied_scaling_factor(c) == 110
    for c in m.fs.cv.pressure_balance.values():
        # This uses the inlet pressure scale
        assert iscale.get_constraint_transform_applied_scaling_factor(c) == 104
Пример #22
0
    def frame_control_volume(self):
        m = ConcreteModel()
        self.configure_class(m)

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

        m.fs.cv = ControlVolume0DBlock(
            default={
                "dynamic": False,
                "has_holdup": False,
                "property_package": m.fs.properties,
                "property_package_args": self.param_args
            })
        m.fs.cv.add_state_blocks(has_phase_equilibrium=False)
        m.fs.cv.add_material_balances()
        m.fs.cv.add_energy_balances()
        m.fs.cv.add_momentum_balances()

        for (v_str, ind), sf in self.scaling_args.items():
            m.fs.properties.set_default_scaling(v_str, sf, index=ind)

        return m
Пример #23
0
def test_get_stream_table_contents_CV0D():
    m = ConcreteModel()
    m.fs = Flowsheet()
    m.fs.pp = PhysicalParameterTestBlock()
    m.fs.u = Unit()
    m.fs.u._setup_dynamics()

    m.fs.u.control_volume = ControlVolume0DBlock(
        default={"property_package": m.fs.pp})

    m.fs.u.control_volume.add_state_blocks(has_phase_equilibrium=False)

    m.fs.u.add_inlet_port()
    m.fs.u.add_outlet_port()

    df = m.fs.u._get_stream_table_contents()

    assert df.loc["a"]["Inlet"] == 1
    assert df.loc["b"]["Inlet"] == 2
    assert df.loc["c"]["Inlet"] == 3

    assert df.loc["a"]["Outlet"] == 1
    assert df.loc["b"]["Outlet"] == 2
    assert df.loc["c"]["Outlet"] == 3
Пример #24
0
    def build(self):
        """
        Begin building model (pre-DAE transformation).

        Args:
            None

        Returns:
            None
        """
        # Call UnitModel.build to setup dynamics
        super(GibbsReactorData, self).build()

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

        self.control_volume.add_state_blocks(has_phase_equilibrium=False)

        self.control_volume.add_total_element_balances()

        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=self.config.has_pressure_change)

        # Add Ports
        self.add_inlet_port()
        self.add_outlet_port()

        # Add performance equations
        # Add Lagrangian multiplier variables
        self.lagrange_mult = Var(self.flowsheet().config.time,
                                 self.config.property_package.element_list,
                                 domain=Reals,
                                 initialize=100,
                                 doc="Lagrangian multipliers")

        # Use Lagrangian multiple method to derive equations for Out_Fi
        # Use RT*lagrange as the Lagrangian multiple such that lagrange is in
        # a similar order of magnitude as log(Yi)

        @self.Constraint(self.flowsheet().config.time,
                         self.config.property_package.phase_list,
                         self.config.property_package.component_list,
                         doc="Gibbs energy minimisation constraint")
        def gibbs_minimization(b, t, p, j):
            # Use natural log of species mole flow to avoid Pyomo solver
            # warnings of reaching infeasible point
            return 0 == (
                b.control_volume.properties_out[t].gibbs_mol_phase_comp[p, j] +
                sum(b.lagrange_mult[t, e] * b.control_volume.properties_out[t].
                    config.parameters.element_comp[j][e]
                    for e in b.config.property_package.element_list))

        # Set references to balance terms at unit level
        if (self.config.has_heat_transfer is True
                and self.config.energy_balance_type != EnergyBalanceType.none):
            add_object_reference(self, "heat_duty", self.control_volume.heat)
        if (self.config.has_pressure_change is True and
                self.config.momentum_balance_type != MomentumBalanceType.none):
            add_object_reference(self, "deltaP", self.control_volume.deltaP)
Пример #25
0
    def build(self):
        """

        Args:
            None

        Returns:
            None
        """
        # Call UnitModel.build
        super(PressureChangerData, self).build()

        # Add a control volume to the unit including setting up dynamics.
        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})

        # Add geomerty variables to control volume
        if self.config.has_holdup:
            self.control_volume.add_geometry()

        # Add inlet and outlet state blocks to control volume
        self.control_volume.add_state_blocks(
                has_phase_equilibrium=self.config.has_phase_equilibrium)

        # Add mass balance
        # Set has_equilibrium is False for now
        # TO DO; set has_equilibrium to True
        self.control_volume.add_material_balances(
                    balance_type=self.config.material_balance_type,
                    has_phase_equilibrium=self.config.has_phase_equilibrium)

        # Add energy balance
        self.control_volume.add_energy_balances(
                    balance_type=self.config.energy_balance_type,
                    has_work_transfer=True)

        # add momentum balance
        self.control_volume.add_momentum_balances(
            balance_type=self.config.momentum_balance_type,
            has_pressure_change=True)

        # Add Ports
        self.add_inlet_port()
        self.add_outlet_port()

        # Set Unit Geometry and holdup Volume
        if self.config.has_holdup is True:
            add_object_reference(self, "volume", self.control_volume.volume)

        # Construct performance equations
        # Set references to balance terms at unit level
        # Add Work transfer variable 'work' as necessary
        add_object_reference(self, "work_mechanical", self.control_volume.work)

        # Add Momentum balance variable 'deltaP' as necessary
        add_object_reference(self, "deltaP", self.control_volume.deltaP)

        # Set reference to scaling factor for pressure in control volume
        add_object_reference(self, "sfp",
                             self.control_volume.scaling_factor_pressure)

        # Set reference to scaling factor for energy in control volume
        add_object_reference(self, "sfe",
                             self.control_volume.scaling_factor_energy)

        # Performance Variables
        self.ratioP = Var(self.flowsheet().config.time, initialize=1.0,
                          doc="Pressure Ratio")

        # Pressure Ratio
        @self.Constraint(self.flowsheet().config.time,
                         doc="Pressure ratio constraint")
        def ratioP_calculation(b, t):
            return (self.sfp*b.ratioP[t] *
                    b.control_volume.properties_in[t].pressure ==
                    self.sfp*b.control_volume.properties_out[t].pressure)

        # Construct equations for thermodynamic assumption
        if self.config.thermodynamic_assumption == \
                ThermodynamicAssumption.isothermal:
            self.add_isothermal()
        elif self.config.thermodynamic_assumption == \
                ThermodynamicAssumption.isentropic:
            self.add_isentropic()
        elif self.config.thermodynamic_assumption == \
                ThermodynamicAssumption.pump:
            self.add_pump()
        elif self.config.thermodynamic_assumption == \
                ThermodynamicAssumption.adiabatic:
            self.add_adiabatic()
Пример #26
0
    def build(self):
        """
        Build the RO model.
        """
        # Call UnitModel.build to setup dynamics
        super().build()

        # for quacking like 1D model -> 0. is "in", 1. is "out"
        self.length_domain = Set(ordered=True,
                                 initialize=(0., 1.))  # inlet/outlet set
        add_object_reference(self, 'difference_elements', self.length_domain)
        self.first_element = self.length_domain.first()

        # Build control volume for feed side
        self.feed_side = ControlVolume0DBlock(
            default={
                "dynamic": False,
                "has_holdup": False,
                "property_package": self.config.property_package,
                "property_package_args": self.config.property_package_args
            })

        self.feed_side.add_state_blocks(has_phase_equilibrium=False)

        self.feed_side.add_material_balances(
            balance_type=self.config.material_balance_type,
            has_mass_transfer=True)

        self.feed_side.add_energy_balances(
            balance_type=self.config.energy_balance_type,
            has_enthalpy_transfer=True)

        self.feed_side.add_momentum_balances(
            balance_type=self.config.momentum_balance_type,
            has_pressure_change=self.config.has_pressure_change)

        # for quacking like 1D model
        add_object_reference(
            self.feed_side, 'properties', {
                **{(t, 0.): self.feed_side.properties_in[t]
                   for t in self.flowsheet().config.time},
                **{(t, 1.): self.feed_side.properties_out[t]
                   for t in self.flowsheet().config.time}
            })

        # Add additional state blocks
        tmp_dict = dict(**self.config.property_package_args)
        tmp_dict["has_phase_equilibrium"] = False
        tmp_dict["parameters"] = self.config.property_package
        tmp_dict["defined_state"] = False  # these blocks are not inlets
        # Build permeate side
        self.permeate_side = self.config.property_package.state_block_class(
            self.flowsheet().config.time,
            self.length_domain,
            doc="Material properties of permeate along permeate channel",
            default=tmp_dict)
        self.mixed_permeate = self.config.property_package.state_block_class(
            self.flowsheet().config.time,
            doc="Material properties of mixed permeate exiting the module",
            default=tmp_dict)
        # Interface properties
        self.feed_side.properties_interface = self.config.property_package.state_block_class(
            self.flowsheet().config.time,
            self.length_domain,
            doc="Material properties of feed-side membrane interface",
            default=tmp_dict)

        # Add Ports
        self.add_inlet_port(name='inlet', block=self.feed_side)
        self.add_outlet_port(name='retentate', block=self.feed_side)
        self.add_port(name='permeate', block=self.mixed_permeate)

        # References for control volume
        # pressure change
        if (self.config.has_pressure_change
                and self.config.momentum_balance_type != 'none'):
            self.deltaP = Reference(self.feed_side.deltaP)

        self._make_performance()

        self._add_expressions()
Пример #27
0
    def build(self):
        # build always starts by calling super().build()
        # This triggers a lot of boilerplate in the background for you
        super().build()
        # this creates blank scaling factors, which are populated later
        self.scaling_factor = Suffix(direction=Suffix.EXPORT)

        # Next, get the base units of measurement from the property definition

        # Create essential sets.
        self.membrane_set = Set(initialize=["cem", "aem"])

        # Create unit model parameters and vars
        self.water_density = Param(
            initialize=1000,
            mutable=False,
            units=pyunits.kg * pyunits.m**-3,
            doc="density of water",
        )

        self.cell_pair_num = Var(
            initialize=1,
            domain=NonNegativeIntegers,
            bounds=(1, 10000),
            units=pyunits.dimensionless,
            doc="cell pair number in a stack",
        )

        # electrodialysis cell dimensional properties
        self.cell_width = Var(
            initialize=0.1,
            bounds=(1e-3, 1e2),
            units=pyunits.meter,
            doc="The width of the electrodialysis cell, denoted as b in the model description",
        )
        self.cell_length = Var(
            initialize=0.5,
            bounds=(1e-3, 1e2),
            units=pyunits.meter,
            doc="The length of the electrodialysis cell, denoted as l in the model description",
        )
        self.spacer_thickness = Var(
            initialize=0.0001,
            units=pyunits.meter,
            doc="The distance between the concecutive aem and cem",
        )

        # Material and Operational properties
        self.membrane_thickness = Var(
            self.membrane_set,
            initialize=0.0001,
            bounds=(1e-6, 1e-1),
            units=pyunits.meter,
            doc="Membrane thickness",
        )
        self.solute_diffusivity_membrane = Var(
            self.membrane_set,
            self.config.property_package.ion_set
            | self.config.property_package.solute_set,
            initialize=1e-10,
            bounds=(1e-16, 1e-6),
            units=pyunits.meter**2 * pyunits.second**-1,
            doc="Solute (ionic and neutral) diffusivity in the membrane phase",
        )
        self.ion_trans_number_membrane = Var(
            self.membrane_set,
            self.config.property_package.ion_set,
            bounds=(0, 1),
            units=pyunits.dimensionless,
            doc="Ion transference number in the membrane phase",
        )
        self.water_trans_number_membrane = Var(
            self.membrane_set,
            initialize=5,
            bounds=(0, 50),
            units=pyunits.dimensionless,
            doc="Transference number of water in membranes",
        )
        self.water_permeability_membrane = Var(
            self.membrane_set,
            initialize=1e-14,
            units=pyunits.meter * pyunits.second**-1 * pyunits.pascal**-1,
            doc="Water permeability coefficient",
        )
        self.membrane_surface_resistance = Var(
            self.membrane_set,
            initialize=2e-4,
            bounds=(1e-6, 1),
            units=pyunits.ohm * pyunits.meter**2,
            doc="Surface resistance of membrane",
        )
        self.electrodes_resistance = Var(
            initialize=0,
            bounds=(0, 100),
            domain=NonNegativeReals,
            units=pyunits.ohm * pyunits.meter**2,
            doc="areal resistance of TWO electrode compartments of a stack",
        )
        self.current = Var(
            self.flowsheet().config.time,
            initialize=1,
            bounds=(0, 1000),
            units=pyunits.amp,
            doc="Current across a cell-pair or stack",
        )
        self.voltage = Var(
            self.flowsheet().config.time,
            initialize=100,
            bounds=(0, 1000),
            units=pyunits.volt,
            doc="Voltage across a stack, declared under the 'Constant Voltage' mode only",
        )
        self.current_utilization = Var(
            initialize=1,
            bounds=(0, 1),
            units=pyunits.dimensionless,
            doc="The current utilization including water electro-osmosis and ion diffusion",
        )

        # Performance metrics
        self.current_efficiency = Var(
            self.flowsheet().config.time,
            initialize=0.9,
            bounds=(0, 1),
            units=pyunits.dimensionless,
            doc="The overall current efficiency for deionizaiton",
        )
        self.power_electrical = Var(
            self.flowsheet().config.time,
            initialize=1,
            bounds=(0, 12100),
            domain=NonNegativeReals,
            units=pyunits.watt,
            doc="Electrical power consumption of a stack",
        )
        self.specific_power_electrical = Var(
            self.flowsheet().config.time,
            initialize=10,
            bounds=(0, 1000),
            domain=NonNegativeReals,
            units=pyunits.kW * pyunits.hour * pyunits.meter**-3,
            doc="Diluate-volume-flow-rate-specific electrical power consumption",
        )
        # TODO: consider adding more performance as needed.

        # Fluxes Vars for constructing mass transfer terms
        self.elec_migration_flux_in = Var(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            units=pyunits.mole * pyunits.meter**-2 * pyunits.second**-1,
            doc="Molar flux_in of a component across the membrane driven by electrical migration",
        )
        self.elec_migration_flux_out = Var(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            units=pyunits.mole * pyunits.meter**-2 * pyunits.second**-1,
            doc="Molar flux_out of a component across the membrane driven by electrical migration",
        )
        self.nonelec_flux_in = Var(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            units=pyunits.mole * pyunits.meter**-2 * pyunits.second**-1,
            doc="Molar flux_in of a component across the membrane driven by non-electrical forces",
        )
        self.nonelec_flux_out = Var(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            units=pyunits.mole * pyunits.meter**-2 * pyunits.second**-1,
            doc="Molar flux_out of a component across the membrane driven by non-electrical forces",
        )

        # Build control volume for the dilute channel
        self.diluate_channel = ControlVolume0DBlock(
            default={
                "dynamic": False,
                "has_holdup": False,
                "property_package": self.config.property_package,
                "property_package_args": self.config.property_package_args,
            }
        )
        self.diluate_channel.add_state_blocks(has_phase_equilibrium=False)
        self.diluate_channel.add_material_balances(
            balance_type=self.config.material_balance_type, has_mass_transfer=True
        )
        self.diluate_channel.add_momentum_balances(
            balance_type=self.config.momentum_balance_type, has_pressure_change=False
        )
        # # TODO: Consider adding energy balances

        # Build control volume for the concentrate channel
        self.concentrate_channel = ControlVolume0DBlock(
            default={
                "dynamic": False,
                "has_holdup": False,
                "property_package": self.config.property_package,
                "property_package_args": self.config.property_package_args,
            }
        )
        self.concentrate_channel.add_state_blocks(has_phase_equilibrium=False)
        self.concentrate_channel.add_material_balances(
            balance_type=self.config.material_balance_type, has_mass_transfer=True
        )
        self.concentrate_channel.add_momentum_balances(
            balance_type=self.config.momentum_balance_type, has_pressure_change=False
        )
        # # TODO: Consider adding energy balances

        # Add ports (creates inlets and outlets for each channel)
        self.add_inlet_port(name="inlet_diluate", block=self.diluate_channel)
        self.add_outlet_port(name="outlet_diluate", block=self.diluate_channel)
        self.add_inlet_port(name="inlet_concentrate", block=self.concentrate_channel)
        self.add_outlet_port(name="outlet_concentrate", block=self.concentrate_channel)

        # Build Constraints
        @self.Constraint(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            doc="Current-Voltage relationship",
        )
        def eq_current_voltage_relation(self, t, p):
            surface_resistance_cp = (
                self.membrane_surface_resistance["aem"]
                + self.membrane_surface_resistance["cem"]
                + self.spacer_thickness
                / (
                    0.5
                    * (
                        self.concentrate_channel.properties_in[
                            t
                        ].electrical_conductivity_phase[p]
                        + self.concentrate_channel.properties_out[
                            t
                        ].electrical_conductivity_phase[p]
                        + self.diluate_channel.properties_in[
                            t
                        ].electrical_conductivity_phase[p]
                        + self.diluate_channel.properties_out[
                            t
                        ].electrical_conductivity_phase[p]
                    )
                )
            )
            return (
                self.current[t]
                * (
                    surface_resistance_cp * self.cell_pair_num
                    + self.electrodes_resistance
                )
                == self.voltage[t] * self.cell_width * self.cell_length
            )

        @self.Constraint(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            doc="Equation for electrical migration flux_in",
        )
        def eq_elec_migration_flux_in(self, t, p, j):
            if j == "H2O":
                return self.elec_migration_flux_in[t, p, j] == (
                    self.water_trans_number_membrane["cem"]
                    + self.water_trans_number_membrane["aem"]
                ) * (
                    self.current[t]
                    / (self.cell_width * self.cell_length)
                    / Constants.faraday_constant
                )
            elif j in self.config.property_package.ion_set:
                return self.elec_migration_flux_in[t, p, j] == (
                    self.ion_trans_number_membrane["cem", j]
                    - self.ion_trans_number_membrane["aem", j]
                ) * (
                    self.current_utilization
                    * self.current[t]
                    / (self.cell_width * self.cell_length)
                ) / (
                    self.config.property_package.charge_comp[j]
                    * Constants.faraday_constant
                )
            else:
                return self.elec_migration_flux_out[t, p, j] == 0

        @self.Constraint(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            doc="Equation for electrical migration flux_out",
        )
        def eq_elec_migration_flux_out(self, t, p, j):
            if j == "H2O":
                return self.elec_migration_flux_out[t, p, j] == (
                    self.water_trans_number_membrane["cem"]
                    + self.water_trans_number_membrane["aem"]
                ) * (
                    self.current[t]
                    / (self.cell_width * self.cell_length)
                    / Constants.faraday_constant
                )
            elif j in self.config.property_package.ion_set:
                return self.elec_migration_flux_out[t, p, j] == (
                    self.ion_trans_number_membrane["cem", j]
                    - self.ion_trans_number_membrane["aem", j]
                ) * (
                    self.current_utilization
                    * self.current[t]
                    / (self.cell_width * self.cell_length)
                ) / (
                    self.config.property_package.charge_comp[j]
                    * Constants.faraday_constant
                )
            else:
                return self.elec_migration_flux_out[t, p, j] == 0

        @self.Constraint(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            doc="Equation for non-electrical flux_in",
        )
        def eq_nonelec_flux_in(self, t, p, j):
            if j == "H2O":
                return self.nonelec_flux_in[
                    t, p, j
                ] == self.water_density / self.config.property_package.mw_comp[j] * (
                    self.water_permeability_membrane["cem"]
                    + self.water_permeability_membrane["aem"]
                ) * (
                    self.concentrate_channel.properties_in[t].pressure_osm_phase[p]
                    - self.diluate_channel.properties_in[t].pressure_osm_phase[p]
                )
            else:
                return self.nonelec_flux_in[t, p, j] == -(
                    self.solute_diffusivity_membrane["cem", j]
                    / self.membrane_thickness["cem"]
                    + self.solute_diffusivity_membrane["aem", j]
                    / self.membrane_thickness["aem"]
                ) * (
                    self.concentrate_channel.properties_in[t].conc_mol_phase_comp[p, j]
                    - self.diluate_channel.properties_in[t].conc_mol_phase_comp[p, j]
                )

        @self.Constraint(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            doc="Equation for non-electrical flux_out",
        )
        def eq_nonelec_flux_out(self, t, p, j):
            if j == "H2O":
                return self.nonelec_flux_out[
                    t, p, j
                ] == self.water_density / self.config.property_package.mw_comp[j] * (
                    self.water_permeability_membrane["cem"]
                    + self.water_permeability_membrane["aem"]
                ) * (
                    self.concentrate_channel.properties_out[t].pressure_osm_phase[p]
                    - self.diluate_channel.properties_out[t].pressure_osm_phase[p]
                )
            else:
                return self.nonelec_flux_out[t, p, j] == -(
                    self.solute_diffusivity_membrane["cem", j]
                    / self.membrane_thickness["cem"]
                    + self.solute_diffusivity_membrane["aem", j]
                    / self.membrane_thickness["aem"]
                ) * (
                    self.concentrate_channel.properties_out[t].conc_mol_phase_comp[p, j]
                    - self.diluate_channel.properties_out[t].conc_mol_phase_comp[p, j]
                )

        # Add constraints for mass transfer terms (diluate_channel)
        @self.Constraint(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            doc="Mass transfer term for the diluate channel",
        )
        def eq_mass_transfer_term_diluate(self, t, p, j):
            return self.diluate_channel.mass_transfer_term[t, p, j] == -0.5 * (
                self.elec_migration_flux_in[t, p, j]
                + self.elec_migration_flux_out[t, p, j]
                + self.nonelec_flux_in[t, p, j]
                + self.nonelec_flux_out[t, p, j]
            ) * (self.cell_width * self.cell_length)

        # Add constraints for mass transfer terms (concentrate_channel)
        @self.Constraint(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            doc="Mass transfer term for the concentrate channel",
        )
        def eq_mass_transfer_term_concentrate(self, t, p, j):
            return self.concentrate_channel.mass_transfer_term[t, p, j] == 0.5 * (
                self.elec_migration_flux_in[t, p, j]
                + self.elec_migration_flux_out[t, p, j]
                + self.nonelec_flux_in[t, p, j]
                + self.nonelec_flux_out[t, p, j]
            ) * (self.cell_width * self.cell_length)

        # Add isothermal condition
        @self.Constraint(
            self.flowsheet().config.time,
            doc="Isothermal condition for the diluate channel",
        )
        def eq_isothermal_diluate(self, t):
            return (
                self.diluate_channel.properties_in[t].temperature
                == self.diluate_channel.properties_out[t].temperature
            )

        @self.Constraint(
            self.flowsheet().config.time,
            doc="Isothermal condition for the concentrate channel",
        )
        def eq_isothermal_concentrate(self, t):
            return (
                self.concentrate_channel.properties_in[t].temperature
                == self.concentrate_channel.properties_out[t].temperature
            )

        @self.Constraint(
            self.flowsheet().config.time,
            doc="Electrical power consumption of a stack",
        )
        def eq_power_electrical(self, t):
            return self.power_electrical[t] == self.current[t] * self.voltage[t]

        @self.Constraint(
            self.flowsheet().config.time,
            doc="Diluate_volume_flow_rate_specific electrical power consumption of a stack",
        )
        def eq_specific_power_electrical(self, t):
            return (
                pyunits.convert(
                    self.specific_power_electrical[t],
                    pyunits.watt * pyunits.second * pyunits.meter**-3,
                )
                * self.diluate_channel.properties_out[t].flow_vol_phase["Liq"]
                == self.current[t] * self.voltage[t]
            )

        @self.Constraint(
            self.flowsheet().config.time,
            doc="Overall current efficiency evaluation",
        )
        def eq_current_efficiency(self, t):
            return (
                self.current_efficiency[t] * self.current[t]
                == sum(
                    self.diluate_channel.properties_in[t].flow_mol_phase_comp["Liq", j]
                    * self.config.property_package.charge_comp[j]
                    - self.diluate_channel.properties_out[t].flow_mol_phase_comp[
                        "Liq", j
                    ]
                    * self.config.property_package.charge_comp[j]
                    for j in self.config.property_package.cation_set
                )
                * Constants.faraday_constant
            )
Пример #28
0
    def build(self):
        # build always starts by calling super().build()
        # This triggers a lot of boilerplate in the background for you
        super().build()

        # this creates blank scaling factors, which are populated later
        self.scaling_factor = Suffix(direction=Suffix.EXPORT)

        # Next, get the base units of measurement from the property definition
        units_meta = self.config.property_package.get_metadata().get_derived_units

        # check the optional config arg 'chemical_additives'
        common_msg = "The 'chemical_additives' dict MUST contain a dict of 'parameter_data' for " + \
                     "each chemical name. That 'parameter_data' dict MUST contain 'mw_chem', " + \
                     "'moles_salt_per_mole_additive', and 'mw_salt' as keys. Users are also " + \
                     "required to provide the values for the molecular weights and the units " + \
                     "within a tuple arg. Example format provided below.\n\n" + \
                     "{'chem_name_1': \n" + \
                     "     {'parameter_data': \n" + \
                     "        {'mw_additive': (value, units), \n" + \
                     "         'moles_salt_per_mole_additive': value, \n" + \
                     "         'mw_salt': (value, units)} \n" + \
                     "     }, \n" + \
                     "}\n\n"
        mw_adds = {}
        mw_salts = {}
        molar_rat = {}
        for j in self.config.chemical_additives:
            if type(self.config.chemical_additives[j]) != dict:
                raise ConfigurationError("\n Did not provide a 'dict' for chemical \n" + common_msg)
            if 'parameter_data' not in self.config.chemical_additives[j]:
                raise ConfigurationError("\n Did not provide a 'parameter_data' for chemical \n" + common_msg)
            if 'mw_additive' not in self.config.chemical_additives[j]['parameter_data']:
                raise ConfigurationError("\n Did not provide a 'mw_additive' for chemical \n" + common_msg)
            if 'moles_salt_per_mole_additive' not in self.config.chemical_additives[j]['parameter_data']:
                raise ConfigurationError("\n Did not provide a 'moles_salt_per_mole_additive' for chemical \n" + common_msg)
            if 'mw_salt' not in self.config.chemical_additives[j]['parameter_data']:
                raise ConfigurationError("\n Did not provide a 'mw_salt' for chemical \n" + common_msg)
            if type(self.config.chemical_additives[j]['parameter_data']['mw_additive']) != tuple:
                raise ConfigurationError("\n Did not provide a tuple for 'mw_additive' \n" + common_msg)
            if type(self.config.chemical_additives[j]['parameter_data']['mw_salt']) != tuple:
                raise ConfigurationError("\n Did not provide a tuple for 'mw_salt' \n" + common_msg)
            if not isinstance(self.config.chemical_additives[j]['parameter_data']['moles_salt_per_mole_additive'], (int,float)):
                raise ConfigurationError("\n Did not provide a number for 'moles_salt_per_mole_additive' \n" + common_msg)

            #Populate temp dicts for parameter and variable setting
            mw_adds[j] = pyunits.convert_value(self.config.chemical_additives[j]['parameter_data']['mw_additive'][0],
                        from_units=self.config.chemical_additives[j]['parameter_data']['mw_additive'][1], to_units=pyunits.kg/pyunits.mol)
            mw_salts[j] = pyunits.convert_value(self.config.chemical_additives[j]['parameter_data']['mw_salt'][0],
                        from_units=self.config.chemical_additives[j]['parameter_data']['mw_salt'][1], to_units=pyunits.kg/pyunits.mol)
            molar_rat[j] = self.config.chemical_additives[j]['parameter_data']['moles_salt_per_mole_additive']

        # Add unit variables
        # Linear relationship between TSS (mg/L) and Turbidity (NTU)
        #           TSS (mg/L) = Turbidity (NTU) * slope + intercept
        #   Default values come from the following paper:
        #       H. Rugner, M. Schwientek,B. Beckingham, B. Kuch, P. Grathwohl,
        #       Environ. Earth Sci. 69 (2013) 373-380. DOI: 10.1007/s12665-013-2307-1
        self.slope = Var(
            self.flowsheet().config.time,
            initialize=1.86,
            bounds=(1e-8, 10),
            domain=NonNegativeReals,
            units=pyunits.mg/pyunits.L,
            doc='Slope relation between TSS (mg/L) and Turbidity (NTU)')

        self.intercept = Var(
            self.flowsheet().config.time,
            initialize=0,
            bounds=(0, 10),
            domain=NonNegativeReals,
            units=pyunits.mg/pyunits.L,
            doc='Intercept relation between TSS (mg/L) and Turbidity (NTU)')

        self.initial_turbidity_ntu = Var(
            self.flowsheet().config.time,
            initialize=50,
            bounds=(0, 10000),
            domain=NonNegativeReals,
            units=pyunits.dimensionless,
            doc='Initial measured Turbidity (NTU) from Jar Test')

        self.final_turbidity_ntu = Var(
            self.flowsheet().config.time,
            initialize=1,
            bounds=(0, 10000),
            domain=NonNegativeReals,
            units=pyunits.dimensionless,
            doc='Final measured Turbidity (NTU) from Jar Test')

        self.chemical_doses = Var(
            self.flowsheet().config.time,
            self.config.chemical_additives.keys(),
            initialize=0,
            bounds=(0, 100),
            domain=NonNegativeReals,
            units=pyunits.mg/pyunits.L,
            doc='Dosages of the set of chemical additives')

        self.chemical_mw = Param(
            self.config.chemical_additives.keys(),
            mutable=True,
            initialize=mw_adds,
            domain=NonNegativeReals,
            units=pyunits.kg/pyunits.mol,
            doc='Molecular weights of the set of chemical additives')

        self.salt_mw = Param(
            self.config.chemical_additives.keys(),
            mutable=True,
            initialize=mw_salts,
            domain=NonNegativeReals,
            units=pyunits.kg/pyunits.mol,
            doc='Molecular weights of the produced salts from chemical additives')

        self.salt_from_additive_mole_ratio = Param(
            self.config.chemical_additives.keys(),
            mutable=True,
            initialize=molar_rat,
            domain=NonNegativeReals,
            units=pyunits.mol/pyunits.mol,
            doc='Moles of the produced salts from 1 mole of chemical additives')


        # Build control volume for feed side
        self.control_volume = ControlVolume0DBlock(default={
            "dynamic": False,
            "has_holdup": False,
            "property_package": self.config.property_package,
            "property_package_args": self.config.property_package_args})

        self.control_volume.add_state_blocks(
            has_phase_equilibrium=False)

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

        # NOTE: This checks for if an energy_balance_type is defined
        if hasattr(self.config, "energy_balance_type"):
            self.control_volume.add_energy_balances(
                balance_type=self.config.energy_balance_type,
                has_enthalpy_transfer=False)

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

        # Add ports
        self.add_inlet_port(name='inlet', block=self.control_volume)
        self.add_outlet_port(name='outlet', block=self.control_volume)

        # Check _phase_component_set for required items
        if ('Liq', 'TDS') not in self.config.property_package._phase_component_set:
            raise ConfigurationError(
                "Coagulation-Flocculation model MUST contain ('Liq','TDS') as a component, but "
                "the property package has only specified the following components {}"
                    .format([p for p in self.config.property_package._phase_component_set]))
        if ('Liq', 'Sludge') not in self.config.property_package._phase_component_set:
            raise ConfigurationError(
                "Coagulation-Flocculation model MUST contain ('Liq','Sludge') as a component, but "
                "the property package has only specified the following components {}"
                    .format([p for p in self.config.property_package._phase_component_set]))
        if ('Liq', 'TSS') not in self.config.property_package._phase_component_set:
            raise ConfigurationError(
                "Coagulation-Flocculation model MUST contain ('Liq','TSS') as a component, but "
                "the property package has only specified the following components {}"
                    .format([p for p in self.config.property_package._phase_component_set]))

        # -------- Add constraints ---------
        # Adds isothermal constraint if no energy balance present
        if not hasattr(self.config, "energy_balance_type"):
            @self.Constraint(self.flowsheet().config.time,
                             doc="Isothermal condition")
            def eq_isothermal(self, t):
                return (self.control_volume.properties_out[t].temperature == self.control_volume.properties_in[t].temperature)

        # Constraint for tss loss rate based on measured final turbidity
        self.tss_loss_rate = Var(
            self.flowsheet().config.time,
            initialize=1,
            bounds=(0, 100),
            domain=NonNegativeReals,
            units=units_meta('mass')*units_meta('time')**-1,
            doc='Mass per time loss rate of TSS based on the measured final turbidity')

        @self.Constraint(self.flowsheet().config.time,
                         doc="Constraint for the loss rate of TSS to be used in mass_transfer_term")
        def eq_tss_loss_rate(self, t):
            tss_out = pyunits.convert(self.slope[t]*self.final_turbidity_ntu[t] + self.intercept[t],
                                    to_units=units_meta('mass')*units_meta('length')**-3)
            input_rate = self.control_volume.properties_in[t].flow_mass_phase_comp['Liq','TSS']
            exit_rate = self.control_volume.properties_out[t].flow_vol_phase['Liq']*tss_out

            return (self.tss_loss_rate[t] == input_rate - exit_rate)

        # Constraint for tds gain rate based on 'chemical_doses' and 'chemical_additives'
        if self.config.chemical_additives:
            self.tds_gain_rate = Var(
                self.flowsheet().config.time,
                initialize=0,
                bounds=(0, 100),
                domain=NonNegativeReals,
                units=units_meta('mass')*units_meta('time')**-1,
                doc='Mass per time gain rate of TDS based on the chemicals added for coagulation')

            @self.Constraint(self.flowsheet().config.time,
                             doc="Constraint for the loss rate of TSS to be used in mass_transfer_term")
            def eq_tds_gain_rate(self, t):
                sum = 0
                for j in self.config.chemical_additives.keys():
                    chem_dose = pyunits.convert(self.chemical_doses[t, j],
                                    to_units=units_meta('mass')*units_meta('length')**-3)
                    chem_dose = chem_dose/self.chemical_mw[j] * \
                            self.salt_from_additive_mole_ratio[j] * \
                            self.salt_mw[j]*self.control_volume.properties_out[t].flow_vol_phase['Liq']
                    sum = sum+chem_dose

                return (self.tds_gain_rate[t] == sum)

        # Add constraints for mass transfer terms
        @self.Constraint(self.flowsheet().config.time,
                         self.config.property_package.phase_list,
                         self.config.property_package.component_list,
                         doc="Mass transfer term")
        def eq_mass_transfer_term(self, t, p, j):
            if (p, j) == ('Liq', 'TSS'):
                return self.control_volume.mass_transfer_term[t, p, j] == -self.tss_loss_rate[t]
            elif (p, j) == ('Liq', 'Sludge'):
                return self.control_volume.mass_transfer_term[t, p, j] == self.tss_loss_rate[t]
            elif (p, j) == ('Liq', 'TDS'):
                if self.config.chemical_additives:
                    return self.control_volume.mass_transfer_term[t, p, j] == self.tds_gain_rate[t]
                else:
                    return self.control_volume.mass_transfer_term[t, p, j] == 0.0
            else:
                return self.control_volume.mass_transfer_term[t, p, j] == 0.0
    def build(self):
        """
        Begin building model.

        Args:
            None

        Returns:
            None
        """
        # Call UnitModel.build to setup dynamics
        super(EquilibriumReactorData, self).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,
                "reaction_package": self.config.reaction_package,
                "reaction_package_args": self.config.reaction_package_args
            })

        # No need for control volume geometry

        self.control_volume.add_state_blocks(
            has_phase_equilibrium=self.config.has_phase_equilibrium)

        self.control_volume.add_reaction_blocks(
            has_equilibrium=self.config.has_equilibrium_reactions)

        self.control_volume.add_material_balances(
            balance_type=self.config.material_balance_type,
            has_rate_reactions=self.config.has_rate_reactions,
            has_equilibrium_reactions=self.config.has_equilibrium_reactions,
            has_phase_equilibrium=self.config.has_phase_equilibrium)

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

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

        # Add Ports
        self.add_inlet_port()
        self.add_outlet_port()

        if self.config.has_rate_reactions:
            # Add equilibrium reactor performance equation
            @self.Constraint(self.flowsheet().config.time,
                             self.config.reaction_package.rate_reaction_idx,
                             doc="Rate reaction equilibrium constraint")
            def rate_reaction_constraint(b, t, r):
                # Set kinetic reaction rates to zero
                return b.control_volume.reactions[t].reaction_rate[r] == 0

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

        if (self.config.has_pressure_change is True
                and self.config.momentum_balance_type != 'none'):
            add_object_reference(self, "deltaP", self.control_volume.deltaP)
Пример #30
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()