예제 #1
0
def phase_equil(b, phase_pair):
    # This method is called via StateBlock.build, thus does not need clean-up
    # try/except statements
    suffix = "_" + phase_pair[0] + "_" + phase_pair[1]

    # Smooth VLE assumes a liquid and a vapor phase, so validate this
    (l_phase, v_phase, vl_comps, henry_comps, l_only_comps,
     v_only_comps) = _valid_VL_component_list(b, phase_pair)

    if l_phase is None or v_phase is None:
        raise ConfigurationError(
            "{} Generic Property Package phase pair {}-{} was set to use "
            "Smooth VLE formulation, however this is not a vapor-liquid pair.".
            format(b.params.name, phase_pair[0], phase_pair[1]))

    # Definition of equilibrium temperature for smooth VLE
    t_units = b.params.get_metadata().default_units["temperature"]
    if v_only_comps == []:
        b.add_component(
            "_t1" + suffix,
            Var(initialize=b.temperature.value,
                doc='Intermediate temperature for calculating Teq',
                units=t_units))
        _t1 = getattr(b, "_t1" + suffix)

        b.add_component(
            "eps_1" + suffix,
            Param(default=0.01,
                  mutable=True,
                  doc='Smoothing parameter for Teq',
                  units=t_units))
        eps_1 = getattr(b, "eps_1" + suffix)

        # PSE paper Eqn 13
        def rule_t1(b):
            return _t1 == smooth_max(b.temperature,
                                     b.temperature_bubble[phase_pair], eps_1)

        b.add_component("_t1_constraint" + suffix, Constraint(rule=rule_t1))
    else:
        _t1 = b.temperature

    if l_only_comps == []:
        b.add_component(
            "eps_2" + suffix,
            Param(default=0.0005,
                  mutable=True,
                  doc='Smoothing parameter for Teq',
                  units=t_units))
        eps_2 = getattr(b, "eps_2" + suffix)

        # PSE paper Eqn 14
        # TODO : Add option for supercritical extension
        def rule_teq(b):
            return b._teq[phase_pair] == smooth_min(
                _t1, b.temperature_dew[phase_pair], eps_2)
    elif v_only_comps == []:

        def rule_teq(b):
            return b._teq[phase_pair] == _t1
    else:

        def rule_teq(b):
            return b._teq[phase_pair] == b.temperature

    b.add_component("_teq_constraint" + suffix, Constraint(rule=rule_teq))
예제 #2
0
    def initialize(blk,
                   state_args_PA=None,
                   state_args_SA=None,
                   outlvl=idaeslog.NOTSET,
                   solver=None,
                   optarg={}):
        '''
        Initialization routine.
        1.- initialize state blocks, using an initial guess for inlet
        primary air and secondary air.
        2.- Use PA and SA values to guess flue gas component molar flowrates,
        Temperature, and Pressure. Initialize flue gas state block.
        3.- Then, solve complete model.

        Keyword Arguments:
            state_args_PA : a dict of arguments to be passed to the property
                           package(s) for the primary air state block to
                           provide an initial state for initialization
                           (see documentation of the specific property package)
                           (default = None).
            state_args_SA : a dict of arguments to be passed to the property
                           package(s) for secondary air state block to
                           provide an initial state for initialization
                           (see documentation of the specific property package)
                           (default = None).
            outlvl : sets output level of initialisation routine
            optarg : solver options dictionary object (default={})
            solver : str indicating whcih solver to use during
                     initialization (default = None, use default solver)

        Returns:
            None
        '''
        init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit")

        # Create solver
        opt = get_solver(solver, optarg)

        # ---------------------------------------------------------------------
        # Initialize inlet property blocks
        blk.primary_air.initialize(outlvl=outlvl,
                                   optarg=optarg,
                                   solver=solver,
                                   state_args=state_args_PA)
        blk.primary_air_moist.initialize(outlvl=outlvl,
                                         optarg=optarg,
                                         solver=solver,
                                         state_args=state_args_PA)
        blk.secondary_air.initialize(outlvl=outlvl,
                                     optarg=optarg,
                                     solver=solver,
                                     state_args=state_args_SA)
        init_log.info_high("Initialization Step 1 Complete.")

        state_args = {
            "flow_mol_comp": {
                "H2O": (blk.primary_air_inlet.flow_mol_comp[0, "H2O"].value +
                        blk.secondary_air_inlet.flow_mol_comp[0, "H2O"].value),
                "CO2": (blk.primary_air_inlet.flow_mol_comp[0, "CO2"].value +
                        blk.secondary_air_inlet.flow_mol_comp[0, "CO2"].value),
                "N2": (blk.primary_air_inlet.flow_mol_comp[0, "N2"].value +
                       blk.secondary_air_inlet.flow_mol_comp[0, "N2"].value),
                "O2": (blk.primary_air_inlet.flow_mol_comp[0, "O2"].value +
                       blk.secondary_air_inlet.flow_mol_comp[0, "O2"].value),
                "SO2": (blk.primary_air_inlet.flow_mol_comp[0, "SO2"].value +
                        blk.secondary_air_inlet.flow_mol_comp[0, "SO2"].value),
                "NO": (blk.primary_air_inlet.flow_mol_comp[0, "NO"].value +
                       blk.secondary_air_inlet.flow_mol_comp[0, "NO"].value)
            },
            "temperature": 1350.00,
            "pressure": blk.primary_air_inlet.pressure[0].value
        }
        # initialize flue gas outlet
        blk.flue_gas.initialize(state_args=state_args,
                                outlvl=outlvl,
                                solver=solver)
        init_log.info_high("Initialization Step 2 Complete.")

        if blk.config.calculate_PA_SA_flows is False:
            # Option 1: given PA and SA component flow rates - fixed inlets
            # fix inlet component molar flow rates
            # unfix ratio_PA2coal, SR, and fluegas_o2_pct_dry
            blk.primary_air_inlet.flow_mol_comp[...].fix()
            blk.secondary_air_inlet.flow_mol_comp[...].fix()
            blk.ratio_PA2coal.unfix()
            blk.SR.unfix()
            blk.fluegas_o2_pct_dry.unfix()
            dof = degrees_of_freedom(blk)

        else:
            # Option 2: SR, ratioPA2_coal to estimate TCA, PA, SA
            # unfix component molar flow rates, but keep T and P fixed.
            blk.primary_air_inlet.flow_mol_comp[:, :].unfix()
            blk.secondary_air_inlet.flow_mol_comp[:, :].unfix()
            dof = degrees_of_freedom(blk)

        if not dof == 0:
            raise ConfigurationError('User needs to check '
                                     'degrees of freedom')

        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = opt.solve(blk, tee=slc.tee)
        init_log.info_high("Initialization Step 3 {}.".format(
            idaeslog.condition(res)))
        init_log.info("Initialization Complete.")
예제 #3
0
    def build(self):
        """Add contents to the block."""
        super().build()
        self._state_block_class = FlueGasStateBlock
        _valid_comps = ["N2", "O2", "NO", "CO2", "H2O", "SO2"]

        for j in self.config.components:
            if j not in _valid_comps:
                raise ConfigurationError(f"Component '{j}' is not supported")
            self.add_component(j, Component())

        # Create Phase object
        self.Vap = VaporPhase()

        # Molecular weight
        self.mw_comp = Param(
            self.component_list,
            initialize={
                k: v
                for k, v in {
                    "O2": 0.0319988,
                    "N2": 0.0280134,
                    "NO": 0.0300061,
                    "CO2": 0.0440095,
                    "H2O": 0.0180153,
                    "SO2": 0.064064,
                }.items() if k in self.component_list
            },
            doc="Molecular Weight [kg/mol]",
            units=pyunits.kg / pyunits.mol,
        )

        # Thermodynamic reference state
        self.pressure_ref = Param(
            within=PositiveReals,
            default=1.01325e5,
            doc="Reference pressure [Pa]",
            units=pyunits.Pa,
        )

        self.temperature_ref = Param(
            within=PositiveReals,
            default=298.15,
            doc="Reference temperature [K]",
            units=pyunits.K,
        )

        # Critical Properties
        self.pressure_crit = Param(
            self.component_list,
            within=PositiveReals,
            initialize={
                k: v
                for k, v in {
                    "O2": 50.45985e5,
                    "N2": 33.943875e5,
                    "NO": 64.85e5,
                    "CO2": 73.8e5,
                    "H2O": 220.64e5,
                    "SO2": 7.883e6,
                }.items() if k in self.component_list
            },
            doc="Critical pressure [Pa]",
            units=pyunits.Pa,
        )

        self.temperature_crit = Param(
            self.component_list,
            within=PositiveReals,
            initialize={
                k: v
                for k, v in {
                    "O2": 154.58,
                    "N2": 126.19,
                    "NO": 180.0,
                    "CO2": 304.18,
                    "H2O": 647,
                    "SO2": 430.8,
                }.items() if k in self.component_list
            },
            doc="Critical temperature [K]",
            units=pyunits.K,
        )

        # Constants for specific heat capacity, enthalpy, and entropy
        # calculations for ideal gas (from NIST 01/08/2020
        # https://webbook.nist.gov/cgi/cbook.cgi?ID=C7727379&Units=SI&Mask=1#Thermo-Gas)
        cp_mol_ig_comp_coeff_parameter_A = {
            k: v
            for k, v in {
                "N2": 19.50583,
                "O2": 30.03235,
                "CO2": 24.99735,
                "H2O": 30.092,
                "NO": 23.83491,
                "SO2": 21.43049,
            }.items() if k in self.component_list
        }
        cp_mol_ig_comp_coeff_parameter_B = {
            k: v
            for k, v in {
                "N2": 19.88705,
                "O2": 8.772972,
                "CO2": 55.18696,
                "H2O": 6.832514,
                "NO": 12.58878,
                "SO2": 74.35094,
            }.items() if k in self.component_list
        }
        cp_mol_ig_comp_coeff_parameter_C = {
            k: v
            for k, v in {
                "N2": -8.598535,
                "O2": -3.98813,
                "CO2": -33.69137,
                "H2O": 6.793435,
                "NO": -1.139011,
                "SO2": -57.75217,
            }.items() if k in self.component_list
        }
        cp_mol_ig_comp_coeff_parameter_D = {
            k: v
            for k, v in {
                "N2": 1.369784,
                "O2": 0.788313,
                "CO2": 7.948387,
                "H2O": -2.53448,
                "NO": -1.497459,
                "SO2": 16.35534,
            }.items() if k in self.component_list
        }
        cp_mol_ig_comp_coeff_parameter_E = {
            k: v
            for k, v in {
                "N2": 0.527601,
                "O2": -0.7416,
                "CO2": -0.136638,
                "H2O": 0.082139,
                "NO": 0.214194,
                "SO2": 0.086731,
            }.items() if k in self.component_list
        }
        cp_mol_ig_comp_coeff_parameter_F = {
            k: v
            for k, v in {
                "N2": -4.935202,
                "O2": -11.3247,
                "CO2": -403.6075,
                "H2O": -250.881,
                "NO": 83.35783,
                "SO2": -305.7688,
            }.items() if k in self.component_list
        }
        cp_mol_ig_comp_coeff_parameter_G = {
            k: v
            for k, v in {
                "N2": 212.39,
                "O2": 236.1663,
                "CO2": 228.2431,
                "H2O": 223.3967,
                "NO": 237.1219,
                "SO2": 254.8872,
            }.items() if k in self.component_list
        }
        cp_mol_ig_comp_coeff_parameter_H = {
            k: v
            for k, v in {
                "N2": 0,
                "O2": 0,
                "CO2": -393.5224,
                "H2O": -241.8264,
                "NO": 90.29114,
                "SO2": -296.8422,
            }.items() if k in self.component_list
        }

        self.cp_mol_ig_comp_coeff_A = Param(
            self.component_list,
            initialize=cp_mol_ig_comp_coeff_parameter_A,
            doc="Constants for spec. heat capacity for ideal gas",
            units=pyunits.J / pyunits.mol / pyunits.K,
        )
        self.cp_mol_ig_comp_coeff_B = Param(
            self.component_list,
            initialize=cp_mol_ig_comp_coeff_parameter_B,
            doc="Constants for spec. heat capacity for ideal gas",
            units=pyunits.J / pyunits.mol / pyunits.K / pyunits.kK,
        )
        self.cp_mol_ig_comp_coeff_C = Param(
            self.component_list,
            initialize=cp_mol_ig_comp_coeff_parameter_C,
            doc="Constants for spec. heat capacity for ideal gas",
            units=pyunits.J / pyunits.mol / pyunits.K / pyunits.kK**2,
        )
        self.cp_mol_ig_comp_coeff_D = Param(
            self.component_list,
            initialize=cp_mol_ig_comp_coeff_parameter_D,
            doc="Constants for spec. heat capacity for ideal gas",
            units=pyunits.J / pyunits.mol / pyunits.K / pyunits.kK**3,
        )
        self.cp_mol_ig_comp_coeff_E = Param(
            self.component_list,
            initialize=cp_mol_ig_comp_coeff_parameter_E,
            doc="Constants for spec. heat capacity for ideal gas",
            units=pyunits.J / pyunits.mol / pyunits.K * pyunits.kK**2,
        )
        self.cp_mol_ig_comp_coeff_F = Param(
            self.component_list,
            initialize=cp_mol_ig_comp_coeff_parameter_F,
            doc="Constants for spec. heat capacity for ideal gas",
            units=pyunits.kJ / pyunits.mol,
        )
        self.cp_mol_ig_comp_coeff_G = Param(
            self.component_list,
            initialize=cp_mol_ig_comp_coeff_parameter_G,
            doc="Constants for spec. heat capacity for ideal gas",
            units=pyunits.J / pyunits.mol / pyunits.K,
        )
        self.cp_mol_ig_comp_coeff_H = Param(
            self.component_list,
            initialize=cp_mol_ig_comp_coeff_parameter_H,
            doc="Constants for spec. heat capacity for ideal gas",
            units=pyunits.kJ / pyunits.mol,
        )

        # Viscosity and thermal conductivity parameters
        self.ce_param = Param(
            initialize=2.6693e-5,
            units=(pyunits.g**0.5 * pyunits.mol**0.5 * pyunits.angstrom**2 *
                   pyunits.K**-0.5 * pyunits.cm**-1 * pyunits.s**-1),
            doc="Parameter for the Chapman-Enskog viscosity correlation",
        )

        self.sigma = Param(
            self.component_list,
            initialize={
                k: v
                for k, v in {
                    "O2": 3.458,
                    "N2": 3.621,
                    "NO": 3.47,
                    "CO2": 3.763,
                    "H2O": 2.605,
                    "SO2": 4.29,
                }.items() if k in self.component_list
            },
            doc="collision diameter",
            units=pyunits.angstrom,
        )
        self.ep_Kappa = Param(
            self.component_list,
            initialize={
                k: v
                for k, v in {
                    "O2": 107.4,
                    "N2": 97.53,
                    "NO": 119.0,
                    "CO2": 244.0,
                    "H2O": 572.4,
                    "SO2": 252.0,
                }.items() if k in self.component_list
            },
            doc=
            "Boltzmann constant divided by characteristic Lennard-Jones energy",
            units=pyunits.K,
        )

        self.set_default_scaling("flow_mol", 1e-4)
        self.set_default_scaling("flow_mass", 1e-3)
        self.set_default_scaling("flow_vol", 1e-3)
        # anything not explicitly listed
        self.set_default_scaling("mole_frac_comp", 1)
        self.set_default_scaling("mole_frac_comp", 1e3, index="NO")
        self.set_default_scaling("mole_frac_comp", 1e3, index="SO2")
        self.set_default_scaling("mole_frac_comp", 1e2, index="H2O")
        self.set_default_scaling("mole_frac_comp", 1e2, index="CO2")
        self.set_default_scaling("flow_vol", 1)

        # For flow_mol_comp, will calculate from flow_mol and mole_frac_comp
        # user should set a scale for both, and for each compoent of
        # mole_frac_comp
        self.set_default_scaling("pressure", 1e-5)
        self.set_default_scaling("temperature", 1e-1)
        self.set_default_scaling("pressure_red", 1e-3)
        self.set_default_scaling("temperature_red", 1)
        self.set_default_scaling("enth_mol_phase", 1e-3)
        self.set_default_scaling("enth_mol", 1e-3)
        self.set_default_scaling("entr_mol", 1e-2)
        self.set_default_scaling("entr_mol_phase", 1e-2)
        self.set_default_scaling("cp_mol", 0.1)
        self.set_default_scaling("cp_mol_phase", 0.1)
        self.set_default_scaling("compress_fact", 1)
        self.set_default_scaling("dens_mol_phase", 1)
        self.set_default_scaling("pressure_sat", 1e-4)
        self.set_default_scaling("visc_d_comp", 1e4)
        self.set_default_scaling("therm_cond_comp", 1e2)
        self.set_default_scaling("visc_d", 1e4)
        self.set_default_scaling("therm_cond", 1e2)
        self.set_default_scaling("mw", 1)
        self.set_default_scaling("mw_comp", 1)
예제 #4
0
파일: tray.py 프로젝트: lneumaier/idaes-pse
    def initialize(self, state_args_feed=None, state_args_liq=None,
                   state_args_vap=None, hold_state_liq=False,
                   hold_state_vap=False, solver=None, optarg=None,
                   outlvl=idaeslog.NOTSET):

        # TODO:
        # 1. Initialization for dynamic mode. Currently not supported.
        # 2. Handle unfixed side split fraction vars
        # 3. Better logic to handle and fix state vars.

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

        init_log.info("Begin initialization.")

        if solver is None:
            init_log.warning("Solver not provided. Default solver(ipopt) "
                             " being used for initialization.")
            solver = get_default_solver()

        if self.config.has_liquid_side_draw:
            if not self.liq_side_sf.fixed:
                raise ConfigurationError(
                    "Liquid side draw split fraction not fixed but "
                    "has_liquid_side_draw set to True.")

        if self.config.has_vapor_side_draw:
            if not self.vap_side_sf.fixed:
                raise ConfigurationError(
                    "Vapor side draw split fraction not fixed but "
                    "has_vapor_side_draw set to True.")

        # Create initial guess if not provided by using current values
        if self.config.is_feed_tray and state_args_feed is None:
            state_args_feed = {}
            state_args_liq = {}
            state_args_vap = {}
            state_dict = (
                self.properties_in_feed[
                    self.flowsheet().config.time.first()]
                .define_port_members())

            for k in state_dict.keys():
                if "flow" in k:
                    if state_dict[k].is_indexed():
                        state_args_feed[k] = {}
                        state_args_liq[k] = {}
                        state_args_vap[k] = {}
                        for m in state_dict[k].keys():
                            state_args_feed[k][m] = \
                                value(state_dict[k][m])
                            state_args_liq[k][m] = \
                                value(0.1 * state_dict[k][m])
                            state_args_vap[k][m] = \
                                value(0.1 * state_dict[k][m])

                    else:
                        state_args_feed[k] = value(state_dict[k])
                        state_args_liq[k] = 0.1 * value(state_dict[k])
                        state_args_vap[k] = 0.1 * value(state_dict[k])
                else:
                    if state_dict[k].is_indexed():
                        state_args_feed[k] = {}
                        state_args_liq[k] = {}
                        state_args_vap[k] = {}
                        for m in state_dict[k].keys():
                            state_args_feed[k][m] = \
                                value(state_dict[k][m])
                            state_args_liq[k][m] = \
                                value(state_dict[k][m])
                            state_args_vap[k][m] = \
                                value(state_dict[k][m])

                    else:
                        state_args_feed[k] = value(state_dict[k])
                        state_args_liq[k] = value(state_dict[k])
                        state_args_vap[k] = value(state_dict[k])

        # Create initial guess if not provided by using current values
        if not self.config.is_feed_tray and state_args_liq is None:
            state_args_liq = {}
            state_dict = (
                self.properties_in_liq[
                    self.flowsheet().config.time.first()]
                .define_port_members())

            for k in state_dict.keys():
                if state_dict[k].is_indexed():
                    state_args_liq[k] = {}
                    for m in state_dict[k].keys():
                        state_args_liq[k][m] = \
                            value(state_dict[k][m])
                else:
                    state_args_liq[k] = value(state_dict[k])

        # Create initial guess if not provided by using current values
        if not self.config.is_feed_tray and state_args_vap is None:
            state_args_vap = {}
            state_dict = (
                self.properties_in_vap[
                    self.flowsheet().config.time.first()]
                .define_port_members())

            for k in state_dict.keys():
                if state_dict[k].is_indexed():
                    state_args_vap[k] = {}
                    for m in state_dict[k].keys():
                        state_args_vap[k][m] = \
                            value(state_dict[k][m])
                else:
                    state_args_vap[k] = value(state_dict[k])

        if self.config.is_feed_tray:
            feed_flags = self.properties_in_feed.initialize(
                outlvl=outlvl,
                solver=solver,
                optarg=optarg,
                hold_state=True,
                state_args=state_args_feed,
                state_vars_fixed=False)

        liq_in_flags = self.properties_in_liq. \
            initialize(outlvl=outlvl,
                       solver=solver,
                       optarg=optarg,
                       hold_state=True,
                       state_args=state_args_liq,
                       state_vars_fixed=False)

        vap_in_flags = self.properties_in_vap. \
            initialize(outlvl=outlvl,
                       solver=solver,
                       optarg=optarg,
                       hold_state=True,
                       state_args=state_args_vap,
                       state_vars_fixed=False)

        # state args to initialize the mixed outlet state block
        state_args_mixed = {}

        if self.config.is_feed_tray:

            # if feed tray, initialize the mixed state block at
            # the same condition.
            state_args_mixed = state_args_feed
        else:
            # if not feed tray, initialize mixed state block at average of
            # vap/liq inlets except pressure. While this is crude, it
            # will work for most combination of state vars.
            state_dict = (
                self.properties_in_liq[
                    self.flowsheet().config.time.first()]
                .define_port_members())
            for k in state_dict.keys():
                if k == "pressure":
                    # Take the lowest pressure and this is the liq inlet
                    state_args_mixed[k] = value(self.properties_in_liq[0].
                                                component(state_dict[k].
                                                          local_name))
                elif state_dict[k].is_indexed():
                    state_args_mixed[k] = {}
                    for m in state_dict[k].keys():
                        if "flow" in k:
                            state_args_mixed[k][m] = \
                                value(self.properties_in_liq[0].
                                      component(state_dict[k].local_name)[m]) \
                                + value(self.properties_in_vap[0].
                                        component(state_dict[k].local_name)[m])

                        else:
                            state_args_mixed[k][m] = \
                                0.5 * (value(self.properties_in_liq[0].
                                             component(state_dict[k].
                                                       local_name)[m]) +
                                       value(self.properties_in_vap[0].
                                       component(state_dict[k].local_name)[m]))

                else:
                    if "flow" in k:
                        state_args_mixed[k] = \
                            value(self.properties_in_liq[0].
                                  component(state_dict[k].local_name)) +\
                            value(self.properties_in_vap[0].
                                  component(state_dict[k].local_name))
                    else:
                        state_args_mixed[k] = \
                            0.5 * (value(self.properties_in_liq[0].
                                         component(state_dict[k].local_name)) +
                                   value(self.properties_in_vap[0].
                                         component(state_dict[k].local_name)))

        # Initialize the mixed outlet state block
        self.properties_out. \
            initialize(outlvl=outlvl,
                       solver=solver,
                       optarg=optarg,
                       hold_state=False,
                       state_args=state_args_mixed,
                       state_vars_fixed=False)

        # Deactivate energy balance
        self.enthalpy_mixing_equations.deactivate()

        # Try fixing the outlet temperature if else pass
        # NOTE: if passed then there would probably be a degree of freedom
        try:
            self.properties_out[:].temperature.\
                fix(state_args_mixed["temperature"])
        except AttributeError:
            init_log.warning("Trying to fix outlet temperature "
                             "during initialization but temperature attribute "
                             "unavailable in the state block. Initialization "
                             "proceeding with a potential degree of freedom.")

        # Deactivate pressure balance
        self.pressure_drop_equation.deactivate()

        # Try fixing the outlet temperature if else pass
        # NOTE: if passed then there would probably be a degree of freedom
        try:
            self.properties_out[:].pressure.\
                fix(state_args_mixed["pressure"])
        except AttributeError:
            init_log.warning("Trying to fix outlet pressure "
                             "during initialization but pressure attribute "
                             "unavailable in the state block. Initialization "
                             "proceeding with a potential degree of freedom.")

        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = solver.solve(self, tee=slc.tee)
        init_log.info(
            "Mass balance solve {}.".format(idaeslog.condition(res))
        )

        # Activate energy balance
        self.enthalpy_mixing_equations.activate()
        try:
            self.properties_out[:].temperature.unfix()
        except AttributeError:
            pass

        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = solver.solve(self, tee=slc.tee)
        init_log.info(
            "Mass and energy balance solve {}.".format(idaeslog.condition(res))
        )

        # Activate pressure balance
        self.pressure_drop_equation.activate()
        try:
            self.properties_out[:].pressure.unfix()
        except AttributeError:
            pass

        if degrees_of_freedom(self) == 0:
            with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
                res = solver.solve(self, tee=slc.tee)
            init_log.info(
                "Mass, energy and pressure balance solve {}.".
                format(idaeslog.condition(res)))
        else:
            raise Exception("State vars fixed but degrees of freedom "
                            "for tray block is not zero during "
                            "initialization.")

        init_log.info(
            "Initialization complete, status {}.".
            format(idaeslog.condition(res)))

        if not self.config.is_feed_tray:
            if not hold_state_vap:
                self.properties_in_vap.release_state(flags=vap_in_flags,
                                                     outlvl=outlvl)
            if not hold_state_liq:
                self.properties_in_liq.release_state(flags=liq_in_flags,
                                                     outlvl=outlvl)
            if hold_state_liq and hold_state_vap:
                return liq_in_flags, vap_in_flags
            elif hold_state_vap:
                return vap_in_flags
            elif hold_state_liq:
                return liq_in_flags
        else:
            self.properties_in_liq.release_state(flags=liq_in_flags,
                                                 outlvl=outlvl)
            self.properties_in_vap.release_state(flags=vap_in_flags,
                                                 outlvl=outlvl)
            return feed_flags
예제 #5
0
    def _setup_dynamics(self):
        """
        This method automates the setting of the dynamic flag and time domain
        for unit models.

        Performs the following:
         1) Determines if this is a top level flowsheet
         2) Gets dynamic flag from parent if not top level, or checks validity
            of argument provided
         3) Checks has_holdup flag if present and dynamic = True

        Args:
            None

        Returns:
            None
        """
        # Get parent object
        if hasattr(self.parent_block(), "config"):
            # Parent block has a config block, so use this
            parent = self.parent_block()
        else:
            # Use parent flowsheet
            try:
                parent = self.flowsheet()
            except ConfigurationError:
                raise DynamicError('{} has no parent flowsheet from which to '
                                   'get dynamic argument. Please provide a '
                                   'value for this argument when constructing '
                                   'the unit.'
                                   .format(self.name))

        # Check the dynamic flag, and retrieve if necessary
        if self.config.dynamic == useDefault:
            # Get flag from parent flowsheet
            try:
                self.config.dynamic = parent.config.dynamic
            except AttributeError:
                # No flowsheet, raise exception
                raise DynamicError('{} parent flowsheet has no dynamic '
                                   'argument. Please provide a '
                                   'value for this argument when constructing '
                                   'the unit.'
                                   .format(self.name))

        # Check for case when dynamic=True, but parent dynamic=False
        if (self.config.dynamic and not parent.config.dynamic):
            raise DynamicError('{} trying to declare a dynamic model within '
                               'a steady-state flowsheet. This is not '
                               'supported by the IDAES framework. Try '
                               'creating a dynamic flowsheet instead, and '
                               'declaring some models as steady-state.'
                               .format(self.name))

        # Set and validate has_holdup argument
        if self.config.has_holdup == useDefault:
            # Default to same value as dynamic flag
            self.config.has_holdup = self.config.dynamic
        elif self.config.has_holdup is False:
            if self.config.dynamic is True:
                # Dynamic model must have has_holdup = True
                raise ConfigurationError(
                            "{} invalid arguments for dynamic and has_holdup. "
                            "If dynamic = True, has_holdup must also be True "
                            "(was False)".format(self.name))
예제 #6
0
파일: ceos.py 프로젝트: jmorgan29/idaes-pse
    def build_parameters(b):
        param_block = b.parent_block()
        if not (b.is_vapor_phase() or b.is_liquid_phase()):
            raise PropertyNotSupportedError(
                "{} received unrecognized phase "
                "name {}. Cubic equation of state supports only Vap and Liq "
                "phases.".format(param_block.name, b))

        if b.config.equation_of_state_options["type"] not in set(
                item for item in CubicType):
            raise ConfigurationError(
                "{} Unrecognized option for equation of "
                "state type: {}. Must be an instance of CubicType "
                "Enum.".format(b.name,
                               b.config.equation_of_state_options["type"]))

        ctype = b.config.equation_of_state_options["type"]
        b._cubic_type = ctype
        cname = ctype.name

        # Check to see if ConfigBlock was created by previous phase
        if hasattr(param_block, cname + "_eos_options"):
            ConfigBlock = getattr(param_block, cname + "_eos_options")
            for key, value in b.config.equation_of_state_options.items():
                if ConfigBlock[key] != value:
                    raise ConfigurationError(
                        "In {}, different {} equation of "
                        "state options for {} are set in different phases, which is "
                        "not supported.".format(b.name, cname, key))
            # Once the options have been validated, we don't have anything
            # left to do
            mixing_rule_a = ConfigBlock["mixing_rule_a"]
            mixing_rule_b = ConfigBlock["mixing_rule_b"]
            b._mixing_rule_a = mixing_rule_a
            b._mixing_rule_b = mixing_rule_b
            return

        setattr(param_block, cname + "_eos_options", deepcopy(CubicConfig))
        ConfigBlock = getattr(param_block, cname + "_eos_options")
        ConfigBlock.set_value(b.config.equation_of_state_options)

        mixing_rule_a = ConfigBlock["mixing_rule_a"]
        mixing_rule_b = ConfigBlock["mixing_rule_b"]
        b._mixing_rule_a = mixing_rule_a
        b._mixing_rule_b = mixing_rule_b

        kappa_data = param_block.config.parameter_data[cname + "_kappa"]
        param_block.add_component(
            cname + '_kappa',
            Var(param_block.component_list,
                param_block.component_list,
                within=Reals,
                initialize=kappa_data,
                doc=cname + ' binary interaction parameters',
                units=None))

        if b._cubic_type == CubicType.PR:
            func_fw = func_fw_PR
        elif b._cubic_type == CubicType.SRK:
            func_fw = func_fw_SRK
        else:
            raise BurntToast(
                "{} received unrecognized cubic type. This should "
                "never happen, so please contact the IDAES developers "
                "with this bug.".format(b.name))
        setattr(param_block, cname + "_func_fw", func_fw)
        setattr(param_block, cname + "_func_alpha", func_alpha_soave)
        setattr(param_block, cname + "_func_dalpha_dT", func_dalpha_dT_soave)
        setattr(param_block, cname + "_func_d2alpha_dT2",
                func_d2alpha_dT2_soave)
예제 #7
0
    def _setup_dynamics(self):
        # Look for parent flowsheet
        fs = self.flowsheet()

        # Check the dynamic flag, and retrieve if necessary
        if self.config.dynamic == useDefault:
            if fs is None:
                # No parent, so default to steady-state and warn user
                _log.warning('{} is a top level flowsheet, but dynamic flag '
                             'set to useDefault. Dynamic '
                             'flag set to False by default'
                             .format(self.name))
                self.config.dynamic = False

            else:
                # Get dynamic flag from parent flowsheet
                self.config.dynamic = fs.config.dynamic

        # Check for case when dynamic=True, but parent dynamic=False
        elif self.config.dynamic is True:
            if fs is not None and fs.config.dynamic is False:
                raise DynamicError(
                        '{} trying to declare a dynamic model within '
                        'a steady-state flowsheet. This is not '
                        'supported by the IDAES framework. Try '
                        'creating a dynamic flowsheet instead, and '
                        'declaring some models as steady-state.'
                        .format(self.name))

        # Validate units for time domain
        if self.config.time is None and fs is not None:
            # We will get units from parent
            pass
        elif self.config.time_units is None and self.config.dynamic:
            raise ConfigurationError(
                f"{self.name} - no units were specified for the time domain. "
                f"Units must be be specified for dynamic models.")
        elif self.config.time_units is None and not self.config.dynamic:
            _log.debug("No units specified for stady-state time domain.")
        elif not isinstance(self.config.time_units, _PyomoUnit):
            raise ConfigurationError(
                "{} unrecognised value for time_units argument. This must be "
                "a Pyomo Unit object (not a compound unit)."
                .format(self.name))

        if self.config.time is not None:
            # Validate user provided time domain
            if (self.config.dynamic is True and
                    not isinstance(self.config.time, ContinuousSet)):
                raise DynamicError(
                        '{} was set as a dynamic flowsheet, but time domain '
                        'provided was not a ContinuousSet.'.format(self.name))
            add_object_reference(self, "_time", self.config.time)
            self._time_units = self.config.time_units
        else:
            # If no parent flowsheet, set up time domain
            if fs is None:
                # Create time domain
                if self.config.dynamic:
                    # Check if time_set has at least two points
                    if len(self.config.time_set) < 2:
                        # Check if time_set is at default value
                        if self.config.time_set == [0.0]:
                            # If default, set default end point to be 1.0
                            self.config.time_set = [0.0, 1.0]
                        else:
                            # Invalid user input, raise Excpetion
                            raise DynamicError(
                                    "Flowsheet provided with invalid "
                                    "time_set attribute - must have at "
                                    "least two values (start and end).")
                    # For dynamics, need a ContinuousSet
                    self._time = ContinuousSet(initialize=self.config.time_set)
                else:
                    # For steady-state, use an ordered Set
                    self._time = pe.Set(initialize=self.config.time_set,
                                        ordered=True)
                self._time_units = self.config.time_units

                # Set time config argument as reference to time domain
                self.config.time = self._time
            else:
                # Set time config argument to parent time
                self.config.time = fs.time
                add_object_reference(self, "_time", fs.time)
                self._time_units = fs._time_units
예제 #8
0
    def initialize(self, state_args_feed=None, state_args_liq=None,
                   state_args_vap=None, solver=None, outlvl=idaeslog.NOTSET):

        # TODO:
        # 1. Check initialization for dynamic mode. Currently not supported.
        # 2. Handle unfixed side split fraction vars
        # 3. Better logic to handle and fix state vars.

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

        init_log.info("Begin initialization.")

        if solver is None:
            init_log.warning("Solver not provided. Default solver(ipopt) "
                             " being used for initialization.")
            solver = get_default_solver()

        if self.config.has_liquid_side_draw:
            if not self.liq_side_sf.fixed:
                raise ConfigurationError(
                    "Liquid side draw split fraction not fixed but "
                    "has_liquid_side_draw set to True.")

        if self.config.has_vapor_side_draw:
            if not self.vap_side_sf.fixed:
                raise ConfigurationError(
                    "Vapor side draw split fraction not fixed but "
                    "has_vapor_side_draw set to True.")

        # Initialize the inlet state blocks
        if self.config.is_feed_tray:
            self.properties_in_feed.initialize(state_args=state_args_feed,
                                               solver=solver,
                                               outlvl=outlvl)
        self.properties_in_liq.initialize(state_args=state_args_liq,
                                          solver=solver,
                                          outlvl=outlvl)
        self.properties_in_vap.initialize(state_args=state_args_vap,
                                          solver=solver,
                                          outlvl=outlvl)

        # state args to initialize the mixed outlet state block
        state_args_mixed = {}

        if self.config.is_feed_tray and state_args_feed is not None:

            # if initial guess provided for the feed stream, initialize the
            # mixed state block at the same condition.
            state_args_mixed = state_args_feed
        else:
            state_dict = \
                self.properties_out[self.flowsheet().config.time.first()].\
                define_state_vars()
            if self.config.is_feed_tray:
                for k in state_dict.keys():
                    if state_dict[k].is_indexed():
                        state_args_mixed[k] = {}
                        for m in state_dict[k].keys():
                            state_args_mixed[k][m] = \
                                self.properties_in_feed[self.flowsheet().
                                                        config.time.first()].\
                                component(state_dict[k].local_name)[m].value
                    else:
                        state_args_mixed[k] = \
                            self.properties_in_feed[self.flowsheet().
                                                    config.time.first()].\
                            component(state_dict[k].local_name).value

            else:
                # if not feed tray, initialize mixed state block at average of
                # vap/liq inlets except pressure. While this is crude, it
                # will work for most combination of state vars.
                for k in state_dict.keys():
                    if k == "pressure":
                        # Take the lowest pressure and this is the liq inlet
                        state_args_mixed[k] = self.properties_in_liq[0].\
                            component(state_dict[k].local_name).value
                    elif state_dict[k].is_indexed():
                        state_args_mixed[k] = {}
                        for m in state_dict[k].keys():
                            state_args_mixed[k][m] = \
                                0.5 * (self.properties_in_liq[0].
                                       component(state_dict[k].local_name)[m].
                                       value + self.properties_in_vap[0].
                                       component(state_dict[k].local_name)[m].
                                       value)
                    else:
                        state_args_mixed[k] = \
                            0.5 * (self.properties_in_liq[0].
                                   component(state_dict[k].local_name).value +
                                   self.properties_in_vap[0].
                                   component(state_dict[k].local_name).value)

        # Initialize the mixed outlet state block
        self.properties_out.initialize(state_args=state_args_mixed,
                                       solver=solver,
                                       outlvl=outlvl)
        # Deactivate energy balance
        self.enthalpy_mixing_equations.deactivate()

        # Try fixing the outlet temperature if else pass
        # NOTE: if passed then there would probably be a degree of freedom
        try:
            self.properties_out[:].temperature.\
                fix(state_args_mixed["temperature"])
        except AttributeError:
            init_log.warning("Trying to fix outlet temperature "
                             "during initialization but temperature attribute "
                             "unavailable in the state block. Initialization "
                             "proceeding with a potential degree of freedom.")

        # Deactivate pressure balance
        self.pressure_drop_equation.deactivate()

        # Try fixing the outlet temperature if else pass
        # NOTE: if passed then there would probably be a degree of freedom
        try:
            self.properties_out[:].pressure.\
                fix(state_args_mixed["pressure"])
        except AttributeError:
            init_log.warning("Trying to fix outlet pressure "
                             "during initialization but pressure attribute "
                             "unavailable in the state block. Initialization "
                             "proceeding with a potential degree of freedom.")

        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = solver.solve(self, tee=slc.tee)
        init_log.info_high(
            "Mass balance solve {}.".format(idaeslog.condition(res))
        )

        # Activate energy balance
        self.enthalpy_mixing_equations.activate()
        try:
            self.properties_out[:].temperature.unfix()
        except AttributeError:
            pass

        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = solver.solve(self, tee=slc.tee)
        init_log.info_high(
            "Mass and energy balance solve {}.".format(idaeslog.condition(res))
        )

        # Activate pressure balance
        self.pressure_drop_equation.activate()

        try:
            self.properties_out[:].pressure.unfix()
        except AttributeError:
            pass

        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = solver.solve(self, tee=slc.tee)
        init_log.info_high(
            "Mass, energy and pressure balance solve {}.".
            format(idaeslog.condition(res)))
        init_log.info(
            "Initialization complete, status {}.".
            format(idaeslog.condition(res)))
예제 #9
0
    def build(self):
        """
        General build method for MixerData. This method calls a number
        of sub-methods which automate the construction of expected attributes
        of unit models.

        Inheriting models should call `super().build`.

        Args:
            None

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

        # Call setup methods from ControlVolumeBlockData
        self._get_property_package()
        self._get_indexing_sets()

        # Create list of inlet names
        inlet_list = self.create_inlet_list()

        # Build StateBlocks
        inlet_blocks = self.add_inlet_state_blocks(inlet_list)

        if self.config.mixed_state_block is None:
            mixed_block = self.add_mixed_state_block()
        else:
            mixed_block = self.get_mixed_state_block()

        if self.config.material_balance_type != MaterialBalanceType.none:
            self.add_material_mixing_equations(inlet_blocks=inlet_blocks,
                                               mixed_block=mixed_block)
        else:
            raise BurntToast("{} received unrecognised value for "
                             "material_mixing_type argument. This "
                             "should not occur, so please contact "
                             "the IDAES developers with this bug.".format(
                                 self.name))

        if self.config.energy_mixing_type == MixingType.extensive:
            self.add_energy_mixing_equations(inlet_blocks=inlet_blocks,
                                             mixed_block=mixed_block)
        elif self.config.energy_mixing_type == MixingType.none:
            pass
        else:
            raise ConfigurationError(
                "{} received unrecognised value for "
                "material_mixing_type argument. This "
                "should not occur, so please contact "
                "the IDAES developers with this bug.".format(self.name))

        if self.config.momentum_mixing_type == MomentumMixingType.minimize:
            self.add_pressure_minimization_equations(inlet_blocks=inlet_blocks,
                                                     mixed_block=mixed_block)
        elif self.config.momentum_mixing_type == MomentumMixingType.equality:
            self.add_pressure_equality_equations(inlet_blocks=inlet_blocks,
                                                 mixed_block=mixed_block)
        elif self.config.momentum_mixing_type == \
                MomentumMixingType.minimize_and_equality:
            self.add_pressure_minimization_equations(inlet_blocks=inlet_blocks,
                                                     mixed_block=mixed_block)
            self.add_pressure_equality_equations(inlet_blocks=inlet_blocks,
                                                 mixed_block=mixed_block)
            self.pressure_equality_constraints.deactivate()
        elif self.config.momentum_mixing_type == MomentumMixingType.none:
            pass
        else:
            raise ConfigurationError(
                "{} recieved unrecognised value for "
                "momentum_mixing_type argument. This "
                "should not occur, so please contact "
                "the IDAES developers with this bug.".format(self.name))

        self.add_port_objects(inlet_list, inlet_blocks, mixed_block)
예제 #10
0
    def build(self):
        '''
        Callable method for Block construction.
        '''
        # Call super.build() to initialize Block
        # In this case we are replicating the super.build to get around a
        # chicken-and-egg problem
        # The super.build tries to validate units, but they have not been set
        # and cannot be set until the config block is created by super.build
        super(ReactionParameterBlock, self).build()
        self.default_scaling_factor = {}

        # Validate and set base units of measurement
        self.get_metadata().add_default_units(self.config.base_units)
        units_meta = self.get_metadata().default_units

        for key, unit in self.config.base_units.items():
            if key in [
                    'time', 'length', 'mass', 'amount', 'temperature',
                    "current", "luminous intensity"
            ]:
                if not isinstance(unit, _PyomoUnit):
                    raise ConfigurationError(
                        "{} recieved unexpected units for quantity {}: {}. "
                        "Units must be instances of a Pyomo unit object.".
                        format(self.name, key, unit))
            else:
                raise ConfigurationError(
                    "{} defined units for an unexpected quantity {}. "
                    "Generic reaction packages only support units for the 7 "
                    "base SI quantities.".format(self.name, key))

        # Check that main 5 base units are assigned
        for k in ['time', 'length', 'mass', 'amount', 'temperature']:
            if not isinstance(units_meta[k], _PyomoUnit):
                raise ConfigurationError(
                    "{} units for quantity {} were not assigned. "
                    "Please make sure to provide units for all base units "
                    "when configuring the reaction package.".format(
                        self.name, k))

        # TODO: Need way to tie reaction package to a specfic property package
        self._validate_property_parameter_units()
        self._validate_property_parameter_properties()

        # Call configure method to set construction arguments
        self.configure()

        # Build core components
        self._reaction_block_class = GenericReactionBlock

        # Alias associated property package to keep line length down
        ppack = self.config.property_package

        if not hasattr(ppack, "_electrolyte") or not ppack._electrolyte:
            pc_set = ppack._phase_component_set
        elif ppack.config.state_components.name == "true":
            pc_set = ppack.true_phase_component_set
        elif ppack.config.state_components.name == "apparent":
            pc_set = ppack.apparent_phase_component_set
        else:
            raise BurntToast()

        # Construct rate reaction attributes if required
        if len(self.config.rate_reactions) > 0:
            # Construct rate reaction index
            self.rate_reaction_idx = Set(
                initialize=self.config.rate_reactions.keys())

            # Construct rate reaction stoichiometry dict
            self.rate_reaction_stoichiometry = {}
            for r, rxn in self.config.rate_reactions.items():
                for p, j in pc_set:
                    self.rate_reaction_stoichiometry[(r, p, j)] = 0

                if rxn.stoichiometry is None:
                    raise ConfigurationError(
                        "{} rate reaction {} was not provided with a "
                        "stoichiometry configuration argument.".format(
                            self.name, r))
                else:
                    for k, v in rxn.stoichiometry.items():
                        if k[0] not in ppack.phase_list:
                            raise ConfigurationError(
                                "{} stoichiometry for rate reaction {} "
                                "included unrecognised phase {}.".format(
                                    self.name, r, k[0]))
                        if k[1] not in ppack.component_list:
                            raise ConfigurationError(
                                "{} stoichiometry for rate reaction {} "
                                "included unrecognised component {}.".format(
                                    self.name, r, k[1]))
                        self.rate_reaction_stoichiometry[(r, k[0], k[1])] = v

                # Check that a method was provided for the rate form
                if rxn.rate_form is None:
                    raise ConfigurationError(
                        "{} rate reaction {} was not provided with a "
                        "rate_form configuration argument.".format(
                            self.name, r))

        # Construct equilibrium reaction attributes if required
        if len(self.config.equilibrium_reactions) > 0:
            # Construct equilibrium reaction index
            self.equilibrium_reaction_idx = Set(
                initialize=self.config.equilibrium_reactions.keys())

            # Construct equilibrium reaction stoichiometry dict
            self.equilibrium_reaction_stoichiometry = {}
            for r, rxn in self.config.equilibrium_reactions.items():
                for p, j in pc_set:
                    self.equilibrium_reaction_stoichiometry[(r, p, j)] = 0

                if rxn.stoichiometry is None:
                    raise ConfigurationError(
                        "{} equilibrium reaction {} was not provided with a "
                        "stoichiometry configuration argument.".format(
                            self.name, r))
                else:
                    for k, v in rxn.stoichiometry.items():
                        if k[0] not in ppack.phase_list:
                            raise ConfigurationError(
                                "{} stoichiometry for equilibrium reaction {} "
                                "included unrecognised phase {}.".format(
                                    self.name, r, k[0]))
                        if k[1] not in ppack.component_list:
                            raise ConfigurationError(
                                "{} stoichiometry for equilibrium reaction {} "
                                "included unrecognised component {}.".format(
                                    self.name, r, k[1]))
                        self.equilibrium_reaction_stoichiometry[(r, k[0],
                                                                 k[1])] = v

                # Check that a method was provided for the equilibrium form
                if rxn.equilibrium_form is None:
                    raise ConfigurationError(
                        "{} equilibrium reaction {} was not provided with a "
                        "equilibrium_form configuration argument.".format(
                            self.name, r))

        # Add a master reaction index which includes both types of reactions
        if (len(self.config.rate_reactions) > 0
                and len(self.config.equilibrium_reactions) > 0):
            self.reaction_idx = Set(
                initialize=(self.rate_reaction_idx
                            | self.equilibrium_reaction_idx))
        elif len(self.config.rate_reactions) > 0:
            self.reaction_idx = Set(initialize=self.rate_reaction_idx)
        elif len(self.config.equilibrium_reactions) > 0:
            self.reaction_idx = Set(initialize=self.equilibrium_reaction_idx)
        else:
            raise BurntToast("{} Generic property package failed to construct "
                             "master reaction Set. This should not happen. "
                             "Please contact the IDAES developers with this "
                             "bug".format(self.name))

        # Construct blocks to contain parameters for each reaction
        for r in self.reaction_idx:
            self.add_component("reaction_" + str(r), Block())

        # Build parameters
        if len(self.config.rate_reactions) > 0:
            for r in self.rate_reaction_idx:
                rblock = getattr(self, "reaction_" + r)
                r_config = self.config.rate_reactions[r]

                order_init = {}
                for p, j in pc_set:
                    if "reaction_order" in r_config.parameter_data:
                        try:
                            order_init[p, j] = r_config.parameter_data[
                                "reaction_order"][p, j]
                        except KeyError:
                            order_init[p, j] = 0
                    else:
                        # Assume elementary reaction and use stoichiometry
                        try:
                            if r_config.stoichiometry[p, j] < 0:
                                # These are reactants, but order is -ve stoic
                                order_init[p,
                                           j] = -r_config.stoichiometry[p, j]
                            else:
                                # Anything else is a product, not be included
                                order_init[p, j] = 0
                        except KeyError:
                            order_init[p, j] = 0

                rblock.reaction_order = Var(pc_set,
                                            initialize=order_init,
                                            doc="Reaction order",
                                            units=None)

                for val in self.config.rate_reactions[r].values():
                    try:
                        val.build_parameters(rblock,
                                             self.config.rate_reactions[r])
                    except AttributeError:
                        pass

        if len(self.config.equilibrium_reactions) > 0:
            for r in self.equilibrium_reaction_idx:
                rblock = getattr(self, "reaction_" + r)
                r_config = self.config.equilibrium_reactions[r]

                order_init = {}
                for p, j in pc_set:
                    if "reaction_order" in r_config.parameter_data:
                        try:
                            order_init[p, j] = r_config.parameter_data[
                                "reaction_order"][p, j]
                        except KeyError:
                            order_init[p, j] = 0
                    else:
                        # Assume elementary reaction and use stoichiometry
                        try:
                            # Here we use the stoic. coeff. directly
                            # However, solids should be excluded as they
                            # normally do not appear in the equilibrium
                            # relationship
                            pobj = ppack.get_phase(p)
                            if not pobj.is_solid_phase():
                                order_init[p, j] = r_config.stoichiometry[p, j]
                            else:
                                order_init[p, j] = 0
                        except KeyError:
                            order_init[p, j] = 0

                rblock.reaction_order = Var(pc_set,
                                            initialize=order_init,
                                            doc="Reaction order",
                                            units=None)

                for val in self.config.equilibrium_reactions[r].values():
                    try:
                        val.build_parameters(
                            rblock, self.config.equilibrium_reactions[r])
                    except AttributeError:
                        pass
                    except KeyError as err:
                        # This likely arises from mismatched true and apparent
                        # species sets. Reaction packages must use the same
                        # basis as the associated thermo properties
                        # Raise an exception to inform the user
                        raise PropertyPackageError(
                            "{} KeyError encountered whilst constructing "
                            "reaction parameters. This may be due to "
                            "mismatched state_components between the "
                            "Reaction Package and the associated Physical "
                            "Property Package - Reaction Packages must use the"
                            "same basis (true or apparent species) as the "
                            "Physical Property Package.".format(self.name),
                            err)

        # As a safety check, make sure all Vars in reaction blocks are fixed
        for v in self.component_objects(Var, descend_into=True):
            for i in v:
                if v[i].value is None:
                    raise ConfigurationError(
                        "{} parameter {} was not assigned"
                        " a value. Please check your configuration "
                        "arguments.".format(self.name, v.local_name))
                v[i].fix()

        # Set default scaling factors
        if self.config.default_scaling_factors is not None:
            self.default_scaling_factor.update(
                self.config.default_scaling_factors)
        # Finally, call populate_default_scaling_factors method to fill blanks
        iscale.populate_default_scaling_factors(self)
예제 #11
0
    def build(self):
        """
        Begin building model (pre-DAE transformation).

        Args:
            None

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

        # Validate list of inert species
        for i in self.config.inert_species:
            if i not in self.config.property_package.component_list:
                raise ConfigurationError(
                    "{} invalid component in inert_species argument. {} is "
                    "not in the property package component list."
                    .format(self.name, i))

        # 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
        e_units = self.config.property_package.get_metadata(
            ).get_derived_units("energy_mole")
        self.lagrange_mult = Var(self.flowsheet().config.time,
                                 self.config.property_package.element_list,
                                 domain=Reals,
                                 initialize=100,
                                 doc="Lagrangian multipliers",
                                 units=e_units)

        # TODO : Remove this once sacling is properly implemented
        self.gibbs_scaling = Param(default=1, mutable=True)

        # 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_component_set,
                         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
            if j in self.config.inert_species:
                return Constraint.Skip
            return 0 == b.gibbs_scaling * (
                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))

        if len(self.config.inert_species) > 0:
            @self.Constraint(self.flowsheet().config.time,
                             self.config.property_package.phase_list,
                             self.config.inert_species,
                             doc="Inert species balances")
            def inert_species_balance(b, t, p, j):
                # Add species balances for inert components
                cv = b.control_volume
                e_comp = cv.properties_out[t].config.parameters.element_comp

                # Check for linear dependence with element balances
                # If an inert species is the only source of element e,
                # the inert species balance would be linearly dependent on the
                # element balance for e.
                dependent = True

                if len(self.config.property_package.phase_list) > 1:
                    # Multiple phases avoid linear dependency
                    dependent = False
                else:
                    for e in self.config.property_package.element_list:
                        if e_comp[j][e] == 0:
                            # Element e not in component j, no effect
                            continue
                        else:
                            for i in self.config.property_package.component_list:
                                if i == j:
                                    continue
                                else:
                                    # If comp j shares element e with comp i
                                    # cannot be linearly dependent
                                    if e_comp[i][e] != 0:
                                        dependent = False

                if (not dependent and (p, j) in
                        self.config.property_package._phase_component_set):
                    return 0 == (
                        cv.properties_in[t].get_material_flow_terms(p, j) -
                        cv.properties_out[t].get_material_flow_terms(p, j))
                else:
                    return Constraint.Skip

        # 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 != MomentumBalanceType.none):
            self.deltaP = Reference(self.control_volume.deltaP[:])
예제 #12
0
    def build(self):
        super().build()

        if self.config.property_package_feed is None:
            raise ConfigurationError(
                "Users must provide a feed property package to the evaporator unit model"
            )
        if self.config.property_package_vapor is None:
            raise ConfigurationError(
                "Users must provide a vapor property package to the evaporator unit model"
            )

        # 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_feed = (
            self.config.property_package_feed.get_metadata().get_derived_units)

        # Add shared unit model variables
        self.U = Var(
            initialize=1e3,
            bounds=(10, 1e4),
            units=pyunits.J * pyunits.s**-1 * pyunits.m**-2 * pyunits.K**-1,
        )

        self.area = Var(initialize=1e2, bounds=(1e-1, 1e4), units=pyunits.m**2)

        self.delta_temperature_in = Var(initialize=1e1,
                                        bounds=(1e-8, 1e3),
                                        units=pyunits.K)

        self.delta_temperature_out = Var(initialize=1e1,
                                         bounds=(1e-8, 1e3),
                                         units=pyunits.K)

        self.lmtd = Var(initialize=1e1, bounds=(1e-8, 1e3), units=pyunits.K)

        # Add feed_side block
        self.feed_side = Block()

        # Add unit variables to feed
        self.feed_side.heat_transfer = Var(initialize=1e4,
                                           bounds=(1, 1e10),
                                           units=pyunits.J * pyunits.s**-1)

        # Add feed_side state blocks
        # Feed state block
        tmp_dict = dict(**self.config.property_package_args_feed)
        tmp_dict["has_phase_equilibrium"] = False
        tmp_dict["parameters"] = self.config.property_package_feed
        tmp_dict["defined_state"] = True  # feed inlet defined
        self.feed_side.properties_feed = (
            self.config.property_package_feed.state_block_class(
                self.flowsheet().config.time,
                doc="Material properties of feed inlet",
                default=tmp_dict,
            ))

        # Brine state block
        tmp_dict["defined_state"] = False  # brine outlet not yet defined
        self.feed_side.properties_brine = (
            self.config.property_package_feed.state_block_class(
                self.flowsheet().config.time,
                doc="Material properties of brine outlet",
                default=tmp_dict,
            ))

        # Vapor state block
        tmp_dict = dict(**self.config.property_package_args_vapor)
        tmp_dict["has_phase_equilibrium"] = False
        tmp_dict["parameters"] = self.config.property_package_vapor
        tmp_dict["defined_state"] = False  # vapor outlet not yet defined
        self.feed_side.properties_vapor = (
            self.config.property_package_vapor.state_block_class(
                self.flowsheet().config.time,
                doc="Material properties of vapor outlet",
                default=tmp_dict,
            ))

        # Add condenser
        self.condenser = Condenser(
            default={"property_package": self.config.property_package_vapor})

        # Add ports - oftentimes users interact with these rather than the state blocks
        self.add_port(name="inlet_feed", block=self.feed_side.properties_feed)
        self.add_port(name="outlet_brine",
                      block=self.feed_side.properties_brine)
        self.add_port(name="outlet_vapor",
                      block=self.feed_side.properties_vapor)
        self.add_port(name="inlet_condenser",
                      block=self.condenser.control_volume.properties_in)
        self.add_port(name="outlet_condenser",
                      block=self.condenser.control_volume.properties_out)

        ### FEED SIDE CONSTRAINTS ###
        # Mass balance
        @self.feed_side.Constraint(
            self.flowsheet().time,
            self.config.property_package_feed.component_list,
            doc="Mass balance",
        )
        def eq_mass_balance(b, t, j):
            lb = b.properties_vapor[t].flow_mass_phase_comp["Liq", "H2O"].lb
            b.properties_vapor[t].flow_mass_phase_comp["Liq", "H2O"].fix(lb)
            if j == "H2O":
                return (
                    b.properties_feed[t].flow_mass_phase_comp["Liq", "H2O"] ==
                    b.properties_brine[t].flow_mass_phase_comp["Liq", "H2O"] +
                    b.properties_vapor[t].flow_mass_phase_comp["Vap", "H2O"])
            else:
                return (b.properties_feed[t].flow_mass_phase_comp["Liq", j] ==
                        b.properties_brine[t].flow_mass_phase_comp["Liq", j])

        # Energy balance
        @self.feed_side.Constraint(self.flowsheet().time, doc="Energy balance")
        def eq_energy_balance(b, t):
            return (b.heat_transfer + b.properties_feed[t].enth_flow ==
                    b.properties_brine[t].enth_flow +
                    b.properties_vapor[t].enth_flow_phase["Vap"])

        # Brine pressure
        @self.feed_side.Constraint(self.flowsheet().time, doc="Brine pressure")
        def eq_brine_pressure(b, t):
            return b.properties_brine[t].pressure == b.properties_brine[
                t].pressure_sat

        # Vapor pressure
        @self.feed_side.Constraint(self.flowsheet().time, doc="Vapor pressure")
        def eq_vapor_pressure(b, t):
            return b.properties_vapor[t].pressure == b.properties_brine[
                t].pressure

        # Vapor temperature
        @self.feed_side.Constraint(self.flowsheet().time,
                                   doc="Vapor temperature")
        def eq_vapor_temperature(b, t):
            return (b.properties_vapor[t].temperature ==
                    b.properties_brine[t].temperature)
            # return b.properties_vapor[t].temperature == 0.5*(b.properties_out[t].temperature + b.properties_in[t].temperature)

        ### EVAPORATOR CONSTRAINTS ###
        # Temperature difference in
        @self.Constraint(self.flowsheet().time,
                         doc="Temperature difference in")
        def eq_delta_temperature_in(b, t):
            return (b.delta_temperature_in ==
                    b.condenser.control_volume.properties_in[t].temperature -
                    b.feed_side.properties_brine[t].temperature)

        # Temperature difference out
        @self.Constraint(self.flowsheet().time,
                         doc="Temperature difference out")
        def eq_delta_temperature_out(b, t):
            return (b.delta_temperature_out ==
                    b.condenser.control_volume.properties_out[t].temperature -
                    b.feed_side.properties_brine[t].temperature)

        # log mean temperature
        @self.Constraint(self.flowsheet().time,
                         doc="Log mean temperature difference")
        def eq_lmtd(b, t):
            dT_in = b.delta_temperature_in
            dT_out = b.delta_temperature_out
            temp_units = pyunits.get_units(dT_in)
            dT_avg = (dT_in + dT_out) / 2
            # external function that ruturns the real root, for the cuberoot of negitive
            # numbers, so it will return without error for positive and negitive dT.
            b.cbrt = ExternalFunction(library=functions_lib(),
                                      function="cbrt",
                                      arg_units=[temp_units**3])
            return b.lmtd == b.cbrt((dT_in * dT_out * dT_avg)) * temp_units

        # Heat transfer between feed side and condenser
        @self.Constraint(self.flowsheet().time, doc="Heat transfer balance")
        def eq_heat_balance(b, t):
            return b.feed_side.heat_transfer == -b.condenser.control_volume.heat[
                t]

        # Evaporator heat transfer
        @self.Constraint(self.flowsheet().time, doc="Evaporator heat transfer")
        def eq_evaporator_heat(b, t):
            return b.feed_side.heat_transfer == b.U * b.area * b.lmtd
예제 #13
0
    def add_material_balances(self,
                              balance_type=MaterialBalanceType.useDefault,
                              **kwargs):
        """
        General method for adding material balances to a control volume.
        This method makes calls to specialised sub-methods for each type of
        material balance.

        Args:
            balance_type - MaterialBalanceType Enum indicating which type of
                    material balance should be constructed.
            has_rate_reactions - whether default generation terms for rate
                    reactions should be included in material balances
            has_equilibrium_reactions - whether generation terms should for
                    chemical equilibrium reactions should be included in
                    material balances
            has_phase_equilibrium - whether generation terms should for phase
                    equilibrium behaviour should be included in material
                    balances
            has_mass_transfer - whether generic mass transfer terms should be
                    included in material balances
            custom_molar_term - a Pyomo Expression representing custom terms to
                    be included in material balances on a molar basis.
            custom_mass_term - a Pyomo Expression representing custom terms to
                    be included in material balances on a mass basis.

        Returns:
            Constraint objects constructed by sub-method
        """
        # Check if balance_type is useDefault, and get default if necessary
        if balance_type == MaterialBalanceType.useDefault:
            try:
                blk = self._get_representative_property_block()
                balance_type = blk.default_material_balance_type()
            except NotImplementedError:
                raise ConfigurationError(
                    "{} property package has not implemented a "
                    "default_material_balance_type, thus cannot use "
                    "MaterialBalanceType.useDefault when constructing "
                    "material balances. Please contact the developer of "
                    "your property package to implement the necessary "
                    "default attributes.".format(self.name))

        self._constructed_material_balance_type = balance_type
        if balance_type == MaterialBalanceType.none:
            mb = None
        elif balance_type == MaterialBalanceType.componentPhase:
            mb = self.add_phase_component_balances(**kwargs)
        elif balance_type == MaterialBalanceType.componentTotal:
            mb = self.add_total_component_balances(**kwargs)
        elif balance_type == MaterialBalanceType.elementTotal:
            mb = self.add_total_element_balances(**kwargs)
        elif balance_type == MaterialBalanceType.total:
            mb = self.add_total_material_balances(**kwargs)
        else:
            raise ConfigurationError(
                "{} invalid balance_type for add_material_balances."
                "Please contact the unit model developer with this bug.".
                format(self.name))

        return mb
예제 #14
0
    def build(self):
        """Build the model.

        Args:
            None
        Returns:
            None
        """
        # Call UnitModel.build to setup dynamics
        super().build()

        # Check phase lists match assumptions
        if self.config.vapor_property_package.phase_list != ["Vap"]:
            raise ConfigurationError(
                f"{self.name} SolventReboiler model requires that the vapor "
                f"phase property package have a single phase named 'Vap'")
        if self.config.liquid_property_package.phase_list != ["Liq"]:
            raise ConfigurationError(
                f"{self.name} SolventReboiler model requires that the liquid "
                f"phase property package have a single phase named 'Liq'")

        # Check for at least one common component in component lists
        if not any(
                j in self.config.vapor_property_package.component_list
                for j in self.config.liquid_property_package.component_list):
            raise ConfigurationError(
                f"{self.name} SolventReboiler model requires that the liquid "
                f"and vapor phase property packages have at least one "
                f"common component.")

        # ---------------------------------------------------------------------
        # Add Control Volume for the Liquid Phase
        self.liquid_phase = ControlVolume0DBlock(
            default={
                "dynamic": self.config.dynamic,
                "has_holdup": self.config.has_holdup,
                "property_package": self.config.liquid_property_package,
                "property_package_args":
                self.config.liquid_property_package_args
            })

        self.liquid_phase.add_state_blocks(has_phase_equilibrium=True)

        # Separate liquid and vapor phases means that phase equilibrium will
        # be handled at the unit model level, thus has_phase_equilibrium is
        # False, but has_mass_transfer is True.
        self.liquid_phase.add_material_balances(
            balance_type=self.config.material_balance_type,
            has_mass_transfer=True,
            has_phase_equilibrium=False)

        # Need to include enthalpy transfer term for the mass transfer
        self.liquid_phase.add_energy_balances(
            balance_type=self.config.energy_balance_type,
            has_heat_transfer=True,
            has_enthalpy_transfer=True)

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

        # ---------------------------------------------------------------------
        # Add single state block for vapor phase
        tmp_dict = dict(**self.config.vapor_property_package_args)
        tmp_dict["has_phase_equilibrium"] = False
        tmp_dict["defined_state"] = False
        self.vapor_phase = \
            self.config.vapor_property_package.build_state_block(
                self.flowsheet().time,
                doc="Vapor phase properties",
                default=tmp_dict)

        # ---------------------------------------------------------------------
        # Check flow basis is compatable
        # TODO : Could add code to convert flow bases, but not now
        t_init = self.flowsheet().time.first()
        if (self.vapor_phase[t_init].get_material_flow_basis() != self.
                liquid_phase.properties_out[t_init].get_material_flow_basis()):
            raise ConfigurationError(
                f"{self.name} vapor and liquid property packages must use the "
                f"same material flow basis.")

        # ---------------------------------------------------------------------
        # Add Ports for the reboiler
        self.add_inlet_port(name="inlet",
                            block=self.liquid_phase,
                            doc="Liquid feed")
        self.add_outlet_port(name="bottoms",
                             block=self.liquid_phase,
                             doc="Bottoms stream")
        self.add_outlet_port(name="vapor_reboil",
                             block=self.vapor_phase,
                             doc="Vapor stream from reboiler")

        # ---------------------------------------------------------------------
        # Add unit level constraints
        # First, need the union and intersection of component lists
        all_comps = (self.vapor_phase.component_list
                     | self.liquid_phase.properties_out.component_list)
        common_comps = (self.vapor_phase.component_list
                        & self.liquid_phase.properties_out.component_list)

        # Get units for unit conversion
        vunits = self.config.vapor_property_package.get_metadata(
        ).get_derived_units
        lunits = self.config.liquid_property_package.get_metadata(
        ).get_derived_units
        flow_basis = self.vapor_phase[t_init].get_material_flow_basis()
        if flow_basis == MaterialFlowBasis.molar:
            fb = "flow_mole"
        elif flow_basis == MaterialFlowBasis.molar:
            fb = "flow_mass"
        else:
            raise ConfigurationError(
                f"{self.name} SolventReboiler only supports mass or molar "
                f"basis for MaterialFlowBasis.")

        if any(j not in common_comps for j in self.vapor_phase.component_list):
            # We have non-condensable components present, need zero-flow param
            self.zero_flow_param = Param(mutable=True,
                                         default=1e-8,
                                         units=vunits("flow_mole"))

        # Material balances
        def rule_material_balance(blk, t, j):
            if j in common_comps:
                # Component is in equilibrium
                # Mass transfer equals vapor flowrate
                return (-blk.liquid_phase.mass_transfer_term[t, "Liq", j] ==
                        pyunits.convert(
                            blk.vapor_phase[t].get_material_flow_terms(
                                "Vap", j),
                            to_units=lunits(fb)))
            elif j in self.vapor_phase.component_list:
                # Non-condensable component
                # No mass transfer term
                # Set vapor flowrate to an arbitary small value
                return (blk.vapor_phase[t].get_material_flow_terms(
                    "Vap", j) == blk.zero_flow_param)
            else:
                # Non-vaporisable comonent
                # Mass transfer term is zero, no vapor flowrate
                return (blk.liquid_phase.mass_transfer_term[t, "Liq", j] == 0 *
                        lunits(fb))

        self.unit_material_balance = Constraint(
            self.flowsheet().time,
            all_comps,
            rule=rule_material_balance,
            doc="Unit level material balances")

        # Phase equilibrium constraints
        # For all common components, equate fugacity in vapor and liquid
        def rule_phase_equilibrium(blk, t, j):
            return (blk.liquid_phase.properties_out[t].fug_phase_comp["Liq", j]
                    == pyunits.convert(blk.vapor_phase[t].fug_phase_comp["Vap",
                                                                         j],
                                       to_units=lunits("pressure")))

        self.unit_phase_equilibrium = Constraint(
            self.flowsheet().time,
            common_comps,
            rule=rule_phase_equilibrium,
            doc="Unit level phase equilibrium constraints")

        # Temperature equality constraint
        def rule_temperature_balance(blk, t):
            return (blk.liquid_phase.properties_out[t].temperature ==
                    pyunits.convert(blk.vapor_phase[t].temperature,
                                    to_units=lunits("temperature")))

        self.unit_temperature_equality = Constraint(
            self.flowsheet().time,
            rule=rule_temperature_balance,
            doc="Unit level temperature equality")

        # Unit level energy balance
        # Energy leaving in vapor phase must be equal and opposite to enthalpy
        # transfer from liquid phase
        def rule_energy_balance(blk, t):
            return (-blk.liquid_phase.enthalpy_transfer[t] == pyunits.convert(
                blk.vapor_phase[t].get_enthalpy_flow_terms("Vap"),
                to_units=lunits("energy") / lunits("time")))

        self.unit_enthalpy_balance = Constraint(
            self.flowsheet().time,
            rule=rule_energy_balance,
            doc="Unit level enthalpy_balance")

        # Pressure balance constraint
        def rule_pressure_balance(blk, t):
            return (
                blk.liquid_phase.properties_out[t].pressure == pyunits.convert(
                    blk.vapor_phase[t].pressure, to_units=lunits("pressure")))

        self.unit_pressure_balance = Constraint(
            self.flowsheet().time,
            rule=rule_pressure_balance,
            doc="Unit level pressure balance")

        # Set references to balance terms at unit level
        self.heat_duty = Reference(self.liquid_phase.heat[:])

        if (self.config.has_pressure_change is True and
                self.config.momentum_balance_type != MomentumBalanceType.none):
            self.deltaP = Reference(self.liquid_phase.deltaP[:])
예제 #15
0
    def add_material_mixing_equations(self, inlet_blocks, mixed_block,
                                      mb_type):
        """
        Add material mixing equations.
        """
        pp = self.config.property_package
        # Get phase component list(s)
        pc_set = mixed_block.phase_component_set

        # Get units metadata
        units = pp.get_metadata()

        flow_basis = mixed_block[
            self.flowsheet().config.time.first()].get_material_flow_basis()
        if flow_basis == MaterialFlowBasis.molar:
            flow_units = units.get_derived_units("flow_mole")
        elif flow_basis == MaterialFlowBasis.mass:
            flow_units = units.get_derived_units("flow_mass")
        else:
            # Let this pass for now with no units
            flow_units = None

        if mb_type == MaterialBalanceType.componentPhase:
            # Create equilibrium generation term and constraints if required
            if self.config.has_phase_equilibrium is True:
                try:
                    self.phase_equilibrium_generation = Var(
                        self.flowsheet().config.time,
                        pp.phase_equilibrium_idx,
                        domain=Reals,
                        doc="Amount of generation in unit by phase equilibria",
                        units=flow_units)
                except AttributeError:
                    raise PropertyNotSupportedError(
                        "{} Property package does not contain a list of phase "
                        "equilibrium reactions (phase_equilibrium_idx), "
                        "thus does not support phase equilibrium.".format(
                            self.name))

            # Define terms to use in mixing equation
            def phase_equilibrium_term(b, t, p, j):
                if self.config.has_phase_equilibrium:
                    sd = {}
                    for r in pp.phase_equilibrium_idx:
                        if pp.phase_equilibrium_list[r][0] == j:
                            if (pp.phase_equilibrium_list[r][1][0] == p):
                                sd[r] = 1
                            elif (pp.phase_equilibrium_list[r][1][1] == p):
                                sd[r] = -1
                            else:
                                sd[r] = 0
                        else:
                            sd[r] = 0

                    return sum(b.phase_equilibrium_generation[t, r] * sd[r]
                               for r in pp.phase_equilibrium_idx)
                else:
                    return 0

            # Write phase-component balances
            @self.Constraint(
                self.flowsheet().config.time,
                pc_set,
                doc="Material mixing equations",
            )
            def material_mixing_equations(b, t, p, j):
                if (p, j) in pc_set:
                    return 0 == (
                        sum(inlet_blocks[i][t].get_material_flow_terms(p, j)
                            for i in range(len(inlet_blocks))) -
                        mixed_block[t].get_material_flow_terms(p, j) +
                        phase_equilibrium_term(b, t, p, j))
                else:
                    return Constraint.Skip

        elif mb_type == MaterialBalanceType.componentTotal:
            # Write phase-component balances
            @self.Constraint(
                self.flowsheet().config.time,
                mixed_block.component_list,
                doc="Material mixing equations",
            )
            def material_mixing_equations(b, t, j):
                return 0 == sum(
                    sum(inlet_blocks[i][t].get_material_flow_terms(p, j)
                        for i in range(len(inlet_blocks))) -
                    mixed_block[t].get_material_flow_terms(p, j)
                    for p in mixed_block.phase_list if (p, j) in pc_set)

        elif mb_type == MaterialBalanceType.total:
            # Write phase-component balances
            @self.Constraint(self.flowsheet().config.time,
                             doc="Material mixing equations")
            def material_mixing_equations(b, t):
                return 0 == sum(
                    sum(
                        sum(inlet_blocks[i][t].get_material_flow_terms(p, j)
                            for i in range(len(inlet_blocks))) -
                        mixed_block[t].get_material_flow_terms(p, j)
                        for j in mixed_block.component_list
                        if (p, j) in pc_set) for p in mixed_block.phase_list)

        elif mb_type == MaterialBalanceType.elementTotal:
            raise ConfigurationError("{} Mixers do not support elemental "
                                     "material balances.".format(self.name))
        elif mb_type == MaterialBalanceType.none:
            pass
        else:
            raise BurntToast(
                "{} Mixer received unrecognised value for "
                "material_balance_type. This should not happen, "
                "please report this bug to the IDAES developers.".format(
                    self.name))
예제 #16
0
    def build(self):
        """Build the model.

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

        # Check config arguments
        if self.config.temperature_spec is None:
            raise ConfigurationError("temperature_spec config argument "
                                     "has not been specified. Please select "
                                     "a valid option.")
        if (self.config.condenser_type == CondenserType.partialCondenser) and \
                (self.config.temperature_spec ==
                 TemperatureSpec.atBubblePoint):
            raise ConfigurationError("condenser_type set to partial but "
                                     "temperature_spec set to atBubblePoint. "
                                     "Select customTemperature and specify "
                                     "outlet temperature.")

        # Add Control Volume for the condenser
        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_state_blocks(has_phase_equilibrium=True)

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

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

        self._make_ports()

        if self.config.condenser_type == CondenserType.totalCondenser:

            self._make_splits_total_condenser()

            if (self.config.temperature_spec == TemperatureSpec.atBubblePoint):
                # Option 1: if true, condition for total condenser
                # (T_cond = T_bubble)
                # Option 2: if this is false, then user has selected
                # custom temperature spec and needs to fix an outlet
                # temperature.
                def rule_total_cond(self, t):
                    return self.control_volume.properties_out[t].\
                        temperature == self.control_volume.properties_out[t].\
                        temperature_bubble

                self.eq_total_cond_spec = Constraint(self.flowsheet().time,
                                                     rule=rule_total_cond)

        else:
            self._make_splits_partial_condenser()

        # Add object reference to variables of the control volume
        # Reference to the heat duty
        add_object_reference(self, "heat_duty", self.control_volume.heat)

        # Reference to the pressure drop (if set to True)
        if self.config.has_pressure_change:
            add_object_reference(self, "deltaP", self.control_volume.deltaP)
예제 #17
0
파일: ceos.py 프로젝트: jmorgan29/idaes-pse
    def common(b, pobj):
        # TODO: determine if Henry's Law applies to Cubic EoS systems
        # For now, raise an exception if found
        # Follow on questions:
        # If Henry's law is used for a component, how does that effect
        # calculating A, B and phi?
        for j in b.component_list:
            cobj = b.params.get_component(j)
            if (cobj.config.henry_component is not None
                    and pobj.local_name in cobj.config.henry_component):
                raise PropertyNotSupportedError(
                    "{} Cubic equations of state do not support Henry's "
                    "components [{}, {}].".format(b.name, pobj.local_name, j))

        ctype = pobj._cubic_type
        cname = pobj.config.equation_of_state_options["type"].name
        mixing_rule_a = pobj._mixing_rule_a
        mixing_rule_b = pobj._mixing_rule_b

        if hasattr(b, cname + "_fw"):
            # Common components already constructed by previous phase
            return

        # Create expressions for coefficients
        def rule_fw(m, j):
            func_fw = getattr(m.params, cname + "_func_fw")
            cobj = m.params.get_component(j)
            return func_fw(cobj)

        b.add_component(
            cname + '_fw',
            Expression(b.component_list, rule=rule_fw, doc='EoS S factor'))

        def rule_a_crit(m, j):
            cobj = m.params.get_component(j)
            return (EoS_param[ctype]['omegaA'] *
                    ((Cubic.gas_constant(b) * cobj.temperature_crit)**2 /
                     cobj.pressure_crit))

        b.add_component(
            cname + '_a_crit',
            Expression(b.component_list,
                       rule=rule_a_crit,
                       doc='Component a coefficient at T_crit'))

        def rule_a(m, j):
            cobj = m.params.get_component(j)
            fw = getattr(m, cname + "_fw")[j]
            ac = getattr(m, cname + '_a_crit')[j]
            func_alpha = getattr(m.params, cname + "_func_alpha")

            return ac * func_alpha(m.temperature, fw, cobj)

        b.add_component(
            cname + '_a',
            Expression(b.component_list,
                       rule=rule_a,
                       doc='Component a coefficient'))

        def rule_da_dT(m, j):
            cobj = m.params.get_component(j)
            fw = getattr(m, cname + "_fw")[j]
            ac = getattr(m, cname + '_a_crit')[j]
            func_dalpha_dT = getattr(m.params, cname + "_func_dalpha_dT")

            return ac * func_dalpha_dT(m.temperature, fw, cobj)

        b.add_component(
            cname + '_da_dT',
            Expression(b.component_list,
                       rule=rule_da_dT,
                       doc='Temperature derivative of component a'))

        def rule_d2a_dT2(m, j):
            cobj = m.params.get_component(j)
            fw = getattr(m, cname + "_fw")[j]
            ac = getattr(m, cname + '_a_crit')[j]
            func_d2alpha_dT2 = getattr(m.params, cname + "_func_d2alpha_dT2")

            return ac * func_d2alpha_dT2(m.temperature, fw, cobj)

        b.add_component(
            cname + '_d2a_dT2',
            Expression(b.component_list,
                       rule=rule_d2a_dT2,
                       doc='Second temperature derivative'
                       'of component a'))

        def func_b(m, j):
            cobj = m.params.get_component(j)
            return (EoS_param[ctype]['coeff_b'] * Cubic.gas_constant(b) *
                    cobj.temperature_crit / cobj.pressure_crit)

        b.add_component(
            cname + '_b',
            Expression(b.component_list,
                       rule=func_b,
                       doc='Component b coefficient'))

        if mixing_rule_a == MixingRuleA.default:

            def rule_am(m, p):
                a = getattr(m, cname + "_a")
                return rule_am_default(m, cname, a, p)

            b.add_component(cname + '_am',
                            Expression(b.phase_list, rule=rule_am))

            def rule_daij_dT(m, i, j):
                a = getattr(m, cname + "_a")
                da_dT = getattr(m, cname + "_da_dT")
                k = getattr(m.params, cname + "_kappa")

                # Include temperature derivative of k for future extension
                dk_ij_dT = 0

                return sqrt(
                    a[i] * a[j]) * (-dk_ij_dT + (1 - k[i, j]) / 2 *
                                    (da_dT[i] / a[i] + da_dT[j] / a[j]))

            b.add_component(
                cname + '_daij_dT',
                Expression(b.component_list,
                           b.component_list,
                           rule=rule_daij_dT))

            def rule_dam_dT(m, p):
                daij_dT = getattr(m, cname + "_daij_dT")
                return sum(
                    sum(m.mole_frac_phase_comp[p, i] *
                        m.mole_frac_phase_comp[p, j] * daij_dT[i, j]
                        for j in m.components_in_phase(p))
                    for i in m.components_in_phase(p))

            b.add_component(cname + "_dam_dT",
                            Expression(b.phase_list, rule=rule_dam_dT))

            def rule_d2am_dT2(m, p):
                k = getattr(m.params, cname + "_kappa")
                a = getattr(m, cname + "_a")
                da_dT = getattr(m, cname + "_da_dT")
                d2a_dT2 = getattr(m, cname + "_d2a_dT2")
                # Placeholders for if temperature dependent k is needed
                dk_dT = 0
                d2k_dT2 = 0

                # Initialize loop variable
                d2am_dT2 = 0

                for i in m.components_in_phase(p):
                    for j in m.components_in_phase(p):
                        d2aij_dT2 = (
                            sqrt(a[i] * a[j]) *
                            (-d2k_dT2 - dk_dT *
                             (da_dT[i] / a[i] + da_dT[j] / a[j]) +
                             (1 - k[i, j]) / 2 *
                             (d2a_dT2[i] / a[i] + d2a_dT2[j] / a[j] - 1 / 2 *
                              (da_dT[i] / a[i] - da_dT[j] / a[j])**2)))
                        d2am_dT2 += (m.mole_frac_phase_comp[p, i] *
                                     m.mole_frac_phase_comp[p, j] * d2aij_dT2)
                return d2am_dT2

            b.add_component(cname + "_d2am_dT2",
                            Expression(b.phase_list, rule=rule_d2am_dT2))

            def rule_delta(m, p, i):
                # See pg. 145 in Properties of Gases and Liquids
                a = getattr(m, cname + "_a")
                am = getattr(m, cname + "_am")
                kappa = getattr(m.params, cname + "_kappa")
                return (2 * sqrt(a[i]) / am[p] *
                        sum(m.mole_frac_phase_comp[p, j] * sqrt(a[j]) *
                            (1 - kappa[i, j])
                            for j in b.components_in_phase(p)))

            b.add_component(cname + "_delta",
                            Expression(b.phase_component_set, rule=rule_delta))

        else:
            raise ConfigurationError(
                "{} Unrecognized option for Equation of State "
                "mixing_rule_a: {}. Must be an instance of MixingRuleA "
                "Enum.".format(b.name, mixing_rule_a))

        if mixing_rule_b == MixingRuleB.default:

            def rule_bm(m, p):
                b = getattr(m, cname + "_b")
                return rule_bm_default(m, b, p)

            b.add_component(cname + '_bm',
                            Expression(b.phase_list, rule=rule_bm))
        else:
            raise ConfigurationError(
                "{} Unrecognized option for Equation of State "
                "mixing_rule_a: {}. Must be an instance of MixingRuleB "
                "Enum.".format(b.name, mixing_rule_b))

        def rule_A(m, p):
            am = getattr(m, cname + "_am")
            return (am[p] * m.pressure /
                    (Cubic.gas_constant(b) * m.temperature)**2)

        b.add_component(cname + '_A', Expression(b.phase_list, rule=rule_A))

        def rule_B(m, p):
            bm = getattr(m, cname + "_bm")
            return (bm[p] * m.pressure /
                    (Cubic.gas_constant(b) * m.temperature))

        b.add_component(cname + '_B', Expression(b.phase_list, rule=rule_B))

        # Add components at equilibrium state if required
        if (b.params.config.phases_in_equilibrium is not None
                and (not b.config.defined_state or b.always_flash)):

            def func_a_eq(m, p1, p2, j):
                cobj = m.params.get_component(j)
                fw = getattr(m, cname + "_fw")[j]
                ac = getattr(m, cname + '_a_crit')[j]
                func_alpha = getattr(m.params, cname + "_func_alpha")

                return ac * func_alpha(m._teq[p1, p2], fw, cobj)

            b.add_component(
                '_' + cname + '_a_eq',
                Expression(b.params._pe_pairs,
                           b.component_list,
                           rule=func_a_eq,
                           doc='Component a coefficient at Teq'))

            def rule_am_eq(m, p1, p2, p3):
                try:
                    rule = m.params.get_phase(
                        p3).config.equation_of_state_options["mixing_rule_a"]
                except (KeyError, TypeError):
                    rule = MixingRuleA.default

                a = getattr(m, "_" + cname + "_a_eq")
                if rule == MixingRuleA.default:
                    return rule_am_default(m, cname, a, p3, (p1, p2))
                else:
                    raise ConfigurationError(
                        "{} Unrecognized option for Equation of State "
                        "mixing_rule_a: {}. Must be an instance of MixingRuleA "
                        "Enum.".format(m.name, rule))

            b.add_component(
                '_' + cname + '_am_eq',
                Expression(b.params._pe_pairs, b.phase_list, rule=rule_am_eq))

            def rule_A_eq(m, p1, p2, p3):
                am_eq = getattr(m, "_" + cname + "_am_eq")
                return (am_eq[p1, p2, p3] * m.pressure /
                        (Cubic.gas_constant(b) * m._teq[p1, p2])**2)

            b.add_component(
                '_' + cname + '_A_eq',
                Expression(b.params._pe_pairs, b.phase_list, rule=rule_A_eq))

            def rule_B_eq(m, p1, p2, p3):
                bm = getattr(m, cname + "_bm")
                return (bm[p3] * m.pressure /
                        (Cubic.gas_constant(b) * m._teq[p1, p2]))

            b.add_component(
                '_' + cname + '_B_eq',
                Expression(b.params._pe_pairs, b.phase_list, rule=rule_B_eq))

            def rule_delta_eq(m, p1, p2, p3, i):
                # See pg. 145 in Properties of Gases and Liquids
                a = getattr(m, "_" + cname + "_a_eq")
                am = getattr(m, "_" + cname + "_am_eq")
                kappa = getattr(m.params, cname + "_kappa")
                return (
                    2 * sqrt(a[p1, p2, i]) / am[p1, p2, p3] *
                    sum(m.mole_frac_phase_comp[p3, j] * sqrt(a[p1, p2, j]) *
                        (1 - kappa[i, j]) for j in m.components_in_phase(p3)))

            b.add_component(
                "_" + cname + "_delta_eq",
                Expression(b.params._pe_pairs,
                           b.phase_component_set,
                           rule=rule_delta_eq))

        # Set up external function calls
        b.add_component("_" + cname + "_ext_func_param",
                        Param(default=ctype.value))
        b.add_component("_" + cname + "_proc_Z_liq",
                        ExternalFunction(library=_so, function="ceos_z_liq"))
        b.add_component("_" + cname + "_proc_Z_vap",
                        ExternalFunction(library=_so, function="ceos_z_vap"))
예제 #18
0
    def build(self):
        super().build()

        self._tech_type = "ozonation"

        build_siso(self)

        if 'toc' not in self.config.property_package.config.solute_list:
            raise ConfigurationError(
                "TOC must be in solute list for Ozonation or Ozone/AOP")

        self.contact_time = Var(self.flowsheet().time,
                                units=pyunits.minute,
                                doc="Ozone contact time")

        self.concentration_time = Var(self.flowsheet().time,
                                      units=(pyunits.mg * pyunits.minute) /
                                      pyunits.liter,
                                      doc="CT value for ozone contactor")

        self.mass_transfer_efficiency = Var(
            self.flowsheet().time,
            units=pyunits.dimensionless,
            doc="Ozone mass transfer efficiency")

        self.specific_energy_coeff = Var(
            self.flowsheet().time,
            units=pyunits.kWh / pyunits.lb,
            bounds=(0, None),
            doc="Specific energy consumption for ozone generation")

        self._fixed_perf_vars.append(self.contact_time)
        self._fixed_perf_vars.append(self.concentration_time)
        self._fixed_perf_vars.append(self.mass_transfer_efficiency)
        self._fixed_perf_vars.append(self.specific_energy_coeff)

        self.ozone_flow_mass = Var(self.flowsheet().time,
                                   units=pyunits.lb / pyunits.hr,
                                   bounds=(0, None),
                                   doc="Mass flow rate of ozone")

        self.ozone_consumption = Var(self.flowsheet().time,
                                     units=pyunits.mg / pyunits.liter,
                                     doc="Ozone consumption")

        self.electricity = Var(self.flowsheet().time,
                               units=pyunits.kW,
                               bounds=(0, None),
                               doc="Ozone generation power demand")

        @self.Constraint(self.flowsheet().time,
                         doc="Ozone consumption constraint")
        def ozone_consumption_constraint(b, t):
            return (b.ozone_consumption[t] == (
                (pyunits.convert(b.properties_in[t].conc_mass_comp['toc'],
                                 to_units=pyunits.mg / pyunits.liter) +
                 self.concentration_time[t] / self.contact_time[t])) /
                    self.mass_transfer_efficiency[t])

        @self.Constraint(self.flowsheet().time,
                         doc='Ozone mass flow constraint')
        def ozone_flow_mass_constraint(b, t):
            return b.ozone_flow_mass[t] == pyunits.convert(
                b.properties_in[t].flow_vol * b.ozone_consumption[t],
                to_units=pyunits.lb / pyunits.hr)

        @self.Constraint(self.flowsheet().time, doc='Ozone power constraint')
        def electricity_constraint(b, t):
            return b.electricity[t] == (b.specific_energy_coeff[t] *
                                        b.ozone_flow_mass[t])

        self._perf_var_dict["Ozone Contact Time (min)"] = self.contact_time
        self._perf_var_dict["Ozone CT Value ((mg*min)/L)"] = \
            self.concentration_time
        self._perf_var_dict["Ozone Mass Transfer Efficiency"] = \
            self.mass_transfer_efficiency
        self._perf_var_dict["Ozone Mass Flow (lb/hr)"] = self.ozone_flow_mass
        self._perf_var_dict["Ozone Unit Power Demand (kW)"] = \
            self.electricity
예제 #19
0
    def build(self):
        # Call UnitModel.build to setup dynamics
        super().build()

        self.scaling_factor = Suffix(direction=Suffix.EXPORT)

        if (len(self.config.property_package.phase_list) > 1
                or 'Liq' not in [p for p in self.config.property_package.phase_list]):
            raise ConfigurationError(
                "NF model only supports one liquid phase ['Liq'],"
                "the property package has specified the following phases {}"
                    .format([p for p in self.config.property_package.phase_list]))

        units_meta = self.config.property_package.get_metadata().get_derived_units

        # TODO: update IDAES such that solvent and solute lists are automatically created on the parameter block
        self.solvent_list = Set()
        self.solute_list = Set()
        for c in self.config.property_package.component_list:
            comp = self.config.property_package.get_component(c)
            try:
                if comp.is_solvent():
                    self.solvent_list.add(c)
                if comp.is_solute():
                    self.solute_list.add(c)
            except TypeError:
                raise ConfigurationError("NF model only supports one solvent and one or more solutes,"
                                         "the provided property package has specified a component '{}' "
                                         "that is not a solvent or solute".format(c))
        if len(self.solvent_list) > 1:
            raise ConfigurationError("NF model only supports one solvent component,"
                                     "the provided property package has specified {} solvent components"
                                     .format(len(self.solvent_list)))

        # Add unit parameters
        self.A_comp = Var(
            self.flowsheet().config.time,
            self.solvent_list,
            initialize=1e-12,
            bounds=(1e-18, 1e-6),
            domain=NonNegativeReals,
            units=units_meta('length')*units_meta('pressure')**-1*units_meta('time')**-1,
            doc='Solvent permeability coeff.')
        self.B_comp = Var(
            self.flowsheet().config.time,
            self.solute_list,
            initialize=1e-8,
            bounds=(1e-11, 1e-5),
            domain=NonNegativeReals,
            units=units_meta('length')*units_meta('time')**-1,
            doc='Solute permeability coeff.')
        self.sigma = Var(
            self.flowsheet().config.time,
            initialize=0.5,
            bounds=(1e-8, 1e6),
            domain=NonNegativeReals,
            units=pyunits.dimensionless,
            doc='Reflection coefficient')
        self.dens_solvent = Param(
            initialize=1000,
            units=units_meta('mass')*units_meta('length')**-3,
            doc='Pure water density')

        # Add unit variables
        self.flux_mass_phase_comp_in = Var(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            initialize=1e-3,
            bounds=(1e-12, 1e6),
            units=units_meta('mass')*units_meta('length')**-2*units_meta('time')**-1,
            doc='Flux at feed inlet')
        self.flux_mass_phase_comp_out = Var(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            initialize=1e-3,
            bounds=(1e-12, 1e6),
            units=units_meta('mass')*units_meta('length')**-2*units_meta('time')**-1,
            doc='Flux at feed outlet')
        self.avg_conc_mass_phase_comp_in = Var(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.solute_list,
            initialize=1e-3,
            bounds=(1e-8, 1e6),
            domain=NonNegativeReals,
            units=units_meta('mass')*units_meta('length')**-3,
            doc='Average solute concentration at feed inlet')
        self.avg_conc_mass_phase_comp_out = Var(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.solute_list,
            initialize=1e-3,
            bounds=(1e-8, 1e6),
            domain=NonNegativeReals,
            units=units_meta('mass')*units_meta('length')**-3,
            doc='Average solute concentration at feed outlet')
        self.area = Var(
            initialize=1,
            bounds=(1e-8, 1e6),
            domain=NonNegativeReals,
            units=units_meta('length') ** 2,
            doc='Membrane area')

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

        # Add permeate block
        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  # permeate block is not an inlet
        self.properties_permeate = self.config.property_package.state_block_class(
            self.flowsheet().config.time,
            doc="Material properties of permeate",
            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.properties_permeate)

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

        # mass transfer
        self.mass_transfer_phase_comp = Var(
            self.flowsheet().config.time,
            self.config.property_package.phase_list,
            self.config.property_package.component_list,
            initialize=1,
            bounds=(1e-8, 1e6),
            domain=NonNegativeReals,
            units=units_meta('mass')*units_meta('time')**-1,
            doc='Mass transfer to permeate')

        @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):
            return self.mass_transfer_phase_comp[t, p, j] == -self.feed_side.mass_transfer_term[t, p, j]

        # NF performance equations
        @self.Expression(self.flowsheet().config.time,
                         self.config.property_package.phase_list,
                         self.config.property_package.component_list,
                         doc="Average flux expression")
        def flux_mass_phase_comp_avg(b, t, p, j):
            return 0.5 * (b.flux_mass_phase_comp_in[t, p, j] + b.flux_mass_phase_comp_out[t, p, j])

        @self.Constraint(self.flowsheet().config.time,
                         self.config.property_package.phase_list,
                         self.config.property_package.component_list,
                         doc="Permeate production")
        def eq_permeate_production(b, t, p, j):
            return (b.properties_permeate[t].get_material_flow_terms(p, j)
                    == b.area * b.flux_mass_phase_comp_avg[t, p, j])

        @self.Constraint(self.flowsheet().config.time,
                         self.config.property_package.phase_list,
                         self.config.property_package.component_list,
                         doc="Inlet water and salt flux")
        def eq_flux_in(b, t, p, j):
            prop_feed = b.feed_side.properties_in[t]
            prop_perm = b.properties_permeate[t]
            comp = self.config.property_package.get_component(j)
            if comp.is_solvent():
                return (b.flux_mass_phase_comp_in[t, p, j] == b.A_comp[t, j] * b.dens_solvent
                        * ((prop_feed.pressure - prop_perm.pressure)
                           - b.sigma[t] * (prop_feed.pressure_osm - prop_perm.pressure_osm)))
            elif comp.is_solute():
                return (b.flux_mass_phase_comp_in[t, p, j] == b.B_comp[t, j]
                        * (prop_feed.conc_mass_phase_comp[p, j] - prop_perm.conc_mass_phase_comp[p, j])
                        + ((1 - b.sigma[t]) * b.flux_mass_phase_comp_in[t, p, j]
                           * 1 / b.dens_solvent * b.avg_conc_mass_phase_comp_in[t, p, j])
                        )

        @self.Constraint(self.flowsheet().config.time,
                         self.config.property_package.phase_list,
                         self.config.property_package.component_list,
                         doc="Outlet water and salt flux")
        def eq_flux_out(b, t, p, j):
            prop_feed = b.feed_side.properties_out[t]
            prop_perm = b.properties_permeate[t]
            comp = self.config.property_package.get_component(j)
            if comp.is_solvent():
                return (b.flux_mass_phase_comp_out[t, p, j] == b.A_comp[t, j] * b.dens_solvent
                        * ((prop_feed.pressure - prop_perm.pressure)
                           - b.sigma[t] * (prop_feed.pressure_osm - prop_perm.pressure_osm)))
            elif comp.is_solute():
                return (b.flux_mass_phase_comp_out[t, p, j] == b.B_comp[t, j]
                        * (prop_feed.conc_mass_phase_comp[p, j] - prop_perm.conc_mass_phase_comp[p, j])
                        + ((1 - b.sigma[t]) * b.flux_mass_phase_comp_out[t, p, j]
                           * 1 / b.dens_solvent * b.avg_conc_mass_phase_comp_out[t, p, j])
                        )

        # Average concentration
        # COMMENT: Chen approximation of logarithmic average implemented
        @self.Constraint(self.flowsheet().config.time,
                         self.config.property_package.phase_list,
                         self.solute_list,
                         doc="Average inlet concentration")
        def eq_avg_conc_in(b, t, p, j):
            prop_feed = b.feed_side.properties_in[t]
            prop_perm = b.properties_permeate[t]
            return (b.avg_conc_mass_phase_comp_in[t, p, j] ==
                    (prop_feed.conc_mass_phase_comp[p, j] * prop_perm.conc_mass_phase_comp[p, j] *
                     (prop_feed.conc_mass_phase_comp[p, j] + prop_perm.conc_mass_phase_comp[p, j])/2)**(1/3))

        @self.Constraint(self.flowsheet().config.time,
                         self.config.property_package.phase_list,
                         self.solute_list,
                         doc="Average inlet concentration")
        def eq_avg_conc_out(b, t, p, j):
            prop_feed = b.feed_side.properties_out[t]
            prop_perm = b.properties_permeate[t]
            return (b.avg_conc_mass_phase_comp_out[t, p, j] ==
                    (prop_feed.conc_mass_phase_comp[p, j] * prop_perm.conc_mass_phase_comp[p, j] *
                     (prop_feed.conc_mass_phase_comp[p, j] + prop_perm.conc_mass_phase_comp[p, j])/2)**(1/3))

        # Feed and permeate-side connection
        @self.Constraint(self.flowsheet().config.time,
                         self.config.property_package.phase_list,
                         self.config.property_package.component_list,
                         doc="Mass transfer from feed to permeate")
        def eq_connect_mass_transfer(b, t, p, j):
            return (b.properties_permeate[t].get_material_flow_terms(p, j)
                    == -b.feed_side.mass_transfer_term[t, p, j])

        @self.Constraint(self.flowsheet().config.time,
                         doc="Enthalpy transfer from feed to permeate")
        def eq_connect_enthalpy_transfer(b, t):
            return (b.properties_permeate[t].get_enthalpy_flow_terms('Liq')
                    == -b.feed_side.enthalpy_transfer[t])

        @self.Constraint(self.flowsheet().config.time,
                         doc="Isothermal assumption for permeate")
        def eq_permeate_isothermal(b, t):
            return b.feed_side.properties_out[t].temperature == \
                   b.properties_permeate[t].temperature
예제 #20
0
파일: mixer.py 프로젝트: AshKot1/idaes-pse
    def add_material_mixing_equations(self, inlet_blocks, mixed_block,
                                      mb_type):
        """
        Add material mixing equations.
        """
        # Get phase component list(s)
        phase_component_list = self._get_phase_comp_list()

        if mb_type == MaterialBalanceType.componentPhase:
            # Create equilibrium generation term and constraints if required
            if self.config.has_phase_equilibrium is True:
                # Get units from property package
                units = {}
                for u in ['holdup', 'time']:
                    try:
                        units[u] = (self.config.property_package.get_metadata(
                        ).default_units[u])
                    except KeyError:
                        units[u] = '-'

                try:
                    self.phase_equilibrium_generation = Var(
                        self.flowsheet().config.time,
                        self.config.property_package.phase_equilibrium_idx,
                        domain=Reals,
                        doc="Amount of generation in unit by phase "
                        "equilibria [{}/{}]".format(units['holdup'],
                                                    units['time']))
                except AttributeError:
                    raise PropertyNotSupportedError(
                        "{} Property package does not contain a list of phase "
                        "equilibrium reactions (phase_equilibrium_idx), "
                        "thus does not support phase equilibrium.".format(
                            self.name))

            # Define terms to use in mixing equation
            def phase_equilibrium_term(b, t, p, j):
                if self.config.has_phase_equilibrium:
                    sd = {}
                    for r in b.config.property_package.phase_equilibrium_idx:
                        if b.config.property_package.\
                                phase_equilibrium_list[r][0] == j:
                            if b.config.property_package.\
                                    phase_equilibrium_list[r][1][0] == p:
                                sd[r] = 1
                            elif b.config.property_package.\
                                    phase_equilibrium_list[r][1][1] == p:
                                sd[r] = -1
                            else:
                                sd[r] = 0
                        else:
                            sd[r] = 0

                    return sum(b.phase_equilibrium_generation[t, r] * sd[r]
                               for r in
                               b.config.property_package.phase_equilibrium_idx)
                else:
                    return 0

            # Write phase-component balances
            @self.Constraint(self.flowsheet().config.time,
                             self.config.property_package.phase_list,
                             self.config.property_package.component_list,
                             doc="Material mixing equations")
            def material_mixing_equations(b, t, p, j):
                if j in phase_component_list[p]:
                    return 0 == (
                        sum(inlet_blocks[i][t].get_material_flow_terms(p, j)
                            for i in range(len(inlet_blocks))) -
                        mixed_block[t].get_material_flow_terms(p, j) +
                        phase_equilibrium_term(b, t, p, j))
                else:
                    return Constraint.Skip

        elif mb_type == MaterialBalanceType.componentTotal:
            # Write phase-component balances
            @self.Constraint(self.flowsheet().config.time,
                             self.config.property_package.component_list,
                             doc="Material mixing equations")
            def material_mixing_equations(b, t, j):
                return 0 == sum(
                    sum(inlet_blocks[i][t].get_material_flow_terms(p, j)
                        for i in range(len(inlet_blocks))) -
                    mixed_block[t].get_material_flow_terms(p, j)
                    for p in b.config.property_package.phase_list)

        elif mb_type == MaterialBalanceType.total:
            # Write phase-component balances
            @self.Constraint(self.flowsheet().config.time,
                             doc="Material mixing equations")
            def material_mixing_equations(b, t):
                return 0 == sum(
                    sum(
                        sum(inlet_blocks[i][t].get_material_flow_terms(p, j)
                            for i in range(len(inlet_blocks))) -
                        mixed_block[t].get_material_flow_terms(p, j)
                        for j in b.config.property_package.component_list)
                    for p in b.config.property_package.phase_list)
        elif mb_type == MaterialBalanceType.elementTotal:
            raise ConfigurationError("{} Mixers do not support elemental "
                                     "material balances.".format(self.name))
        elif mb_type == MaterialBalanceType.none:
            pass
        else:
            raise BurntToast(
                "{} Mixer received unrecognised value for "
                "material_balance_type. This should not happen, "
                "please report this bug to the IDAES developers.".format(
                    self.name))
예제 #21
0
    def build(self):
        """Build the model.

        Args:
            None
        Returns:
            None
        """
        # Setup model build logger
        model_log = idaeslog.getModelLogger(self.name, tag="unit")

        # Call UnitModel.build to setup dynamics
        super(CondenserData, self).build()

        # Check config arguments
        if self.config.temperature_spec is None:
            raise ConfigurationError("temperature_spec config argument "
                                     "has not been specified. Please select "
                                     "a valid option.")
        if (self.config.condenser_type == CondenserType.partialCondenser) and \
                (self.config.temperature_spec ==
                 TemperatureSpec.atBubblePoint):
            raise ConfigurationError("condenser_type set to partial but "
                                     "temperature_spec set to atBubblePoint. "
                                     "Select customTemperature and specify "
                                     "outlet temperature.")

        # Add Control Volume for the condenser
        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_state_blocks(has_phase_equilibrium=True)

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

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

        # Note: No momentum balance added for the condenser as the condenser
        # outlet pressure is a spec set by the user.

        # Get liquid and vapor phase objects from the property package
        # to be used below. Avoids repition.
        _liquid_list = []
        _vapor_list = []
        for p in self.config.property_package.phase_list:
            pobj = self.config.property_package.get_phase(p)
            if pobj.is_vapor_phase():
                _vapor_list.append(p)
            elif pobj.is_liquid_phase():
                _liquid_list.append(p)
            else:
                _liquid_list.append(p)
                model_log.warning(
                    "A non-liquid/non-vapor phase was detected but will "
                    "be treated as a liquid.")

        # Create a pyomo set for indexing purposes. This set is appended to
        # model otherwise results in an abstract set.
        self._liquid_set = Set(initialize=_liquid_list)
        self._vapor_set = Set(initialize=_vapor_list)

        self._make_ports()

        if self.config.condenser_type == CondenserType.totalCondenser:

            self._make_splits_total_condenser()

            if (self.config.temperature_spec == TemperatureSpec.atBubblePoint):
                # Option 1: if true, condition for total condenser
                # (T_cond = T_bubble)
                # Option 2: if this is false, then user has selected
                # custom temperature spec and needs to fix an outlet
                # temperature.

                # Get index for bubble point temperature and and assume it
                # will have only a single phase equilibrium pair. This is to
                # support the generic property framework where the T_bubble
                # is indexed by the phases_in_equilibrium. In distillation,
                # the assumption is that there will only be a single pair
                # i.e. vap-liq.
                idx = next(
                    iter(self.control_volume.properties_out[self.flowsheet(
                    ).config.time.first()].temperature_bubble))

                def rule_total_cond(self, t):
                    return self.control_volume.properties_out[t].\
                        temperature == self.control_volume.properties_out[t].\
                        temperature_bubble[idx]

                self.eq_total_cond_spec = Constraint(self.flowsheet().time,
                                                     rule=rule_total_cond)

        else:
            self._make_splits_partial_condenser()

        # Add object reference to variables of the control volume
        # Reference to the heat duty
        self.heat_duty = Reference(self.control_volume.heat[:])

        self.condenser_pressure = Reference(
            self.control_volume.properties_out[:].pressure)
예제 #22
0
    def initialize(blk,
                   state_args=None,
                   outlvl=idaeslog.NOTSET,
                   solver=None,
                   optarg={}):
        '''
        Downcomer initialization routine.

        Keyword Arguments:
            state_args : a dict of arguments to be passed to the property
                           package(s) for the control_volume of the model to
                           provide an initial state for initialization
                           (see documentation of the specific property package)
                           (default = None).
            outlvl : sets output level of initialisation routine
            optarg : solver options dictionary object (default={})
            solver : str indicating whcih solver to use during
                     initialization (default = None, use default solver)

        Returns:
            None
        '''
        init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit")

        # Create solver
        opt = get_solver(solver, optarg)

        init_log.info_low("Starting initialization...")

        flags = blk.control_volume.initialize(
            outlvl=outlvl + 1,
            optarg=optarg,
            solver=solver,
            state_args=state_args,
        )
        init_log.info_high("Initialization Step 1 Complete.")
        # make sure 0 DoF
        if degrees_of_freedom(blk) != 0:
            raise ConfigurationError(
                "Incorrect degrees of freedom when initializing {}: dof = {}".
                format(blk.name, degrees_of_freedom(blk)))
        # Fix outlet pressure
        for t in blk.flowsheet().config.time:
            blk.control_volume.properties_out[t].pressure.fix(
                value(blk.control_volume.properties_in[t].pressure))
        blk.pressure_change_total_eqn.deactivate()

        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = opt.solve(blk, tee=slc.tee)
        init_log.info_high("Initialization Step 2 {}.".format(
            idaeslog.condition(res)))

        # Unfix outlet enthalpy and pressure
        for t in blk.flowsheet().config.time:
            blk.control_volume.properties_out[t].pressure.unfix()
        blk.pressure_change_total_eqn.activate()

        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = opt.solve(blk, tee=slc.tee)
        init_log.info_high("Initialization Step 3 {}.".format(
            idaeslog.condition(res)))
        blk.control_volume.release_state(flags, outlvl + 1)
        init_log.info("Initialization Complete.")
예제 #23
0
    def calculate_state(
        self,
        var_args=None,
        hold_state=False,
        outlvl=idaeslog.NOTSET,
        solver=None,
        optarg=None,
    ):
        """
        Solves state blocks given a set of variables and their values. These variables can
        be state variables or properties. This method is typically used before
        initialization to solve for state variables because non-state variables (i.e. properties)
        cannot be fixed in initialization routines.

        Keyword Arguments:
            var_args : dictionary with variables and their values, they can be state variables or properties
                       {(VAR_NAME, INDEX): VALUE}
            hold_state : flag indicating whether all of the state variables should be fixed after calculate state.
                         True - State variables will be fixed.
                         False - State variables will remain unfixed, unless already fixed.
            outlvl : idaes logger object that sets output level of solve call (default=idaeslog.NOTSET)
            solver : solver name string if None is provided the default solver
                     for IDAES will be used (default = None)
            optarg : solver options dictionary object (default={})

        Returns:
            results object from state block solve
        """
        # Get logger
        solve_log = idaeslog.getSolveLogger(self.name,
                                            level=outlvl,
                                            tag="properties")

        # Initialize at current state values (not user provided)
        self.initialize(solver=solver, optarg=optarg, outlvl=outlvl)

        # Set solver and options
        opt = get_solver(solver, optarg)

        # Fix variables and check degrees of freedom
        flags = (
            {}
        )  # dictionary noting which variables were fixed and their previous state
        for k in self.keys():
            sb = self[k]
            for (v_name, ind), val in var_args.items():
                var = getattr(sb, v_name)
                if iscale.get_scaling_factor(var[ind]) is None:
                    _log.warning(
                        "While using the calculate_state method on {sb_name}, variable {v_name} "
                        "was provided as an argument in var_args, but it does not have a scaling "
                        "factor. This suggests that the calculate_scaling_factor method has not been "
                        "used or the variable was created on demand after the scaling factors were "
                        "calculated. It is recommended to touch all relevant variables (i.e. call "
                        "them or set an initial value) before using the calculate_scaling_factor "
                        "method.".format(v_name=v_name, sb_name=sb.name))
                if var[ind].is_fixed():
                    flags[(k, v_name, ind)] = True
                    if value(var[ind]) != val:
                        raise ConfigurationError(
                            "While using the calculate_state method on {sb_name}, {v_name} was "
                            "fixed to a value {val}, but it was already fixed to value {val_2}. "
                            "Unfix the variable before calling the calculate_state "
                            "method or update var_args."
                            "".format(
                                sb_name=sb.name,
                                v_name=var.name,
                                val=val,
                                val_2=value(var[ind]),
                            ))
                else:
                    flags[(k, v_name, ind)] = False
                    var[ind].fix(val)

            if degrees_of_freedom(sb) != 0:
                raise RuntimeError(
                    "While using the calculate_state method on {sb_name}, the degrees "
                    "of freedom were {dof}, but 0 is required. Check var_args and ensure "
                    "the correct fixed variables are provided."
                    "".format(sb_name=sb.name, dof=degrees_of_freedom(sb)))

        # Solve
        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            results = solve_indexed_blocks(opt, [self], tee=slc.tee)
            solve_log.info_high("Calculate state: {}.".format(
                idaeslog.condition(results)))

        if not check_optimal_termination(results):
            _log.warning(
                "While using the calculate_state method on {sb_name}, the solver failed "
                "to converge to an optimal solution. This suggests that the user provided "
                "infeasible inputs, or that the model is poorly scaled, poorly initialized, "
                "or degenerate.")

        # unfix all variables fixed with var_args
        for (k, v_name, ind), previously_fixed in flags.items():
            if not previously_fixed:
                var = getattr(self[k], v_name)
                var[ind].unfix()

        # fix state variables if hold_state
        if hold_state:
            fix_state_vars(self)

        return results
예제 #24
0
    def build(self):
        """
        Begin building model (pre-DAE transformation).

        Args:
            None

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

        # Set flow directions for the control volume blocks and specify
        # dicretisation if not specified.
        if self.config.flow_type == HeatExchangerFlowPattern.cocurrent:
            set_direction_shell = FlowDirection.forward
            set_direction_tube = FlowDirection.forward
            if (self.config.shell_side.transformation_method !=
                    self.config.tube_side.transformation_method) or (
                        self.config.shell_side.transformation_scheme !=
                        self.config.tube_side.transformation_scheme):
                raise ConfigurationError(
                    "HeatExchanger1D only supports similar transformation "
                    "schemes on the shell side and tube side domains for "
                    "both cocurrent and countercurrent flow patterns.")
            if self.config.shell_side.transformation_method is useDefault:
                _log.warning("Discretization method was "
                             "not specified for the shell side of the "
                             "co-current heat exchanger. "
                             "Defaulting to finite "
                             "difference method on the shell side.")
                self.config.shell_side.transformation_method = "dae.finite_difference"
            if self.config.tube_side.transformation_method is useDefault:
                _log.warning("Discretization method was "
                             "not specified for the tube side of the "
                             "co-current heat exchanger. "
                             "Defaulting to finite "
                             "difference method on the tube side.")
                self.config.tube_side.transformation_method = "dae.finite_difference"
            if self.config.shell_side.transformation_scheme is useDefault:
                _log.warning("Discretization scheme was "
                             "not specified for the shell side of the "
                             "co-current heat exchanger. "
                             "Defaulting to backward finite "
                             "difference on the shell side.")
                self.config.shell_side.transformation_scheme = "BACKWARD"
            if self.config.tube_side.transformation_scheme is useDefault:
                _log.warning("Discretization scheme was "
                             "not specified for the tube side of the "
                             "co-current heat exchanger. "
                             "Defaulting to backward finite "
                             "difference on the tube side.")
                self.config.tube_side.transformation_scheme = "BACKWARD"
        elif self.config.flow_type == HeatExchangerFlowPattern.countercurrent:
            set_direction_shell = FlowDirection.forward
            set_direction_tube = FlowDirection.backward
            if self.config.shell_side.transformation_method is useDefault:
                _log.warning("Discretization method was "
                             "not specified for the shell side of the "
                             "counter-current heat exchanger. "
                             "Defaulting to finite "
                             "difference method on the shell side.")
                self.config.shell_side.transformation_method = "dae.finite_difference"
            if self.config.tube_side.transformation_method is useDefault:
                _log.warning("Discretization method was "
                             "not specified for the tube side of the "
                             "counter-current heat exchanger. "
                             "Defaulting to finite "
                             "difference method on the tube side.")
                self.config.tube_side.transformation_method = "dae.finite_difference"
            if self.config.shell_side.transformation_scheme is useDefault:
                _log.warning("Discretization scheme was "
                             "not specified for the shell side of the "
                             "counter-current heat exchanger. "
                             "Defaulting to backward finite "
                             "difference on the shell side.")
                self.config.shell_side.transformation_scheme = "BACKWARD"
            if self.config.tube_side.transformation_scheme is useDefault:
                _log.warning("Discretization scheme was "
                             "not specified for the tube side of the "
                             "counter-current heat exchanger. "
                             "Defaulting to forward finite "
                             "difference on the tube side.")
                self.config.tube_side.transformation_scheme = "BACKWARD"
        else:
            raise ConfigurationError(
                "{} HeatExchanger1D only supports cocurrent and "
                "countercurrent flow patterns, but flow_type configuration"
                " argument was set to {}.".format(self.name,
                                                  self.config.flow_type))

        # Control volume 1D for shell
        self.shell = ControlVolume1DBlock(
            default={
                "dynamic": self.config.shell_side.dynamic,
                "has_holdup": self.config.shell_side.has_holdup,
                "property_package": self.config.shell_side.property_package,
                "property_package_args":
                self.config.shell_side.property_package_args,
                "transformation_method":
                self.config.shell_side.transformation_method,
                "transformation_scheme":
                self.config.shell_side.transformation_scheme,
                "finite_elements": self.config.finite_elements,
                "collocation_points": self.config.collocation_points,
            })

        self.tube = ControlVolume1DBlock(
            default={
                "dynamic": self.config.tube_side.dynamic,
                "has_holdup": self.config.tube_side.has_holdup,
                "property_package": self.config.tube_side.property_package,
                "property_package_args":
                self.config.tube_side.property_package_args,
                "transformation_method":
                self.config.tube_side.transformation_method,
                "transformation_scheme":
                self.config.tube_side.transformation_scheme,
                "finite_elements": self.config.finite_elements,
                "collocation_points": self.config.collocation_points,
            })

        self.shell.add_geometry(flow_direction=set_direction_shell)
        self.tube.add_geometry(flow_direction=set_direction_tube)

        self.shell.add_state_blocks(
            information_flow=set_direction_shell,
            has_phase_equilibrium=self.config.shell_side.has_phase_equilibrium,
        )
        self.tube.add_state_blocks(
            information_flow=set_direction_tube,
            has_phase_equilibrium=self.config.tube_side.has_phase_equilibrium,
        )

        # Populate shell
        self.shell.add_material_balances(
            balance_type=self.config.shell_side.material_balance_type,
            has_phase_equilibrium=self.config.shell_side.has_phase_equilibrium,
        )

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

        self.shell.add_momentum_balances(
            balance_type=self.config.shell_side.momentum_balance_type,
            has_pressure_change=self.config.shell_side.has_pressure_change,
        )

        self.shell.apply_transformation()

        # Populate tube
        self.tube.add_material_balances(
            balance_type=self.config.tube_side.material_balance_type,
            has_phase_equilibrium=self.config.tube_side.has_phase_equilibrium,
        )

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

        self.tube.add_momentum_balances(
            balance_type=self.config.tube_side.momentum_balance_type,
            has_pressure_change=self.config.tube_side.has_pressure_change,
        )

        self.tube.apply_transformation()

        # Add Ports for shell side
        self.add_inlet_port(name="shell_inlet", block=self.shell)
        self.add_outlet_port(name="shell_outlet", block=self.shell)

        # Add Ports for tube side
        self.add_inlet_port(name="tube_inlet", block=self.tube)
        self.add_outlet_port(name="tube_outlet", block=self.tube)

        # Add reference to control volume geometry
        add_object_reference(self, "shell_area", self.shell.area)
        add_object_reference(self, "shell_length", self.shell.length)
        add_object_reference(self, "tube_area", self.tube.area)
        add_object_reference(self, "tube_length", self.tube.length)

        self._make_performance()
예제 #25
0
    def initialize(self,
                   state_args=None,
                   solver=None,
                   optarg=None,
                   outlvl=idaeslog.NOTSET):

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

        solverobj = get_solver(solver, optarg)

        # Initialize the inlet and outlet state blocks. Calling the state
        # blocks initialize methods directly so that custom set of state args
        # can be passed to the inlet and outlet state blocks as control_volume
        # initialize method initializes the state blocks with the same
        # state conditions.
        flags = self.control_volume.properties_in. \
            initialize(state_args=state_args,
                       solver=solver,
                       optarg=optarg,
                       outlvl=outlvl,
                       hold_state=True)

        # Initialize outlet state block at same conditions of inlet except
        # the temperature. Set the temperature to a temperature guess based
        # on the desired boilup_ratio.

        # Get index for bubble point temperature and and assume it
        # will have only a single phase equilibrium pair. This is to
        # support the generic property framework where the T_bubble
        # is indexed by the phases_in_equilibrium. In distillation,
        # the assumption is that there will only be a single pair
        # i.e. vap-liq.
        idx = next(
            iter(self.control_volume.properties_in[0].temperature_bubble))
        temp_guess = 0.5 * (
            value(self.control_volume.properties_in[0].temperature_dew[idx]) -
            value(self.control_volume.properties_in[0].
                  temperature_bubble[idx])) + \
            value(self.control_volume.properties_in[0].temperature_bubble[idx])

        state_args_outlet = {}
        state_dict_outlet = (self.control_volume.properties_in[
            self.flowsheet().time.first()].define_port_members())

        for k in state_dict_outlet.keys():
            if state_dict_outlet[k].is_indexed():
                state_args_outlet[k] = {}
                for m in state_dict_outlet[k].keys():
                    state_args_outlet[k][m] = value(state_dict_outlet[k][m])
            else:
                if k != "temperature":
                    state_args_outlet[k] = value(state_dict_outlet[k])
                else:
                    state_args_outlet[k] = temp_guess

        self.control_volume.properties_out.initialize(
            state_args=state_args_outlet,
            solver=solver,
            optarg=optarg,
            outlvl=outlvl,
            hold_state=False)

        if degrees_of_freedom(self) == 0:
            with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
                res = solverobj.solve(self, tee=slc.tee)
            init_log.info("Initialization Complete, {}.".format(
                idaeslog.condition(res)))
        else:
            raise ConfigurationError(
                "State vars fixed but degrees of freedom "
                "for reboiler is not zero during "
                "initialization. Please ensure that the boilup_ratio "
                "or the outlet temperature is fixed.")

        if not check_optimal_termination(res):
            raise InitializationError(
                f"{self.name} failed to initialize successfully. Please check "
                f"the output logs for more information.")

        self.control_volume.properties_in.\
            release_state(flags=flags, outlvl=outlvl)
예제 #26
0
def define_state(b):
    # FpcTP contains full information on the phase equilibrium, so flash
    # calculations re not always needed
    b.always_flash = False

    # Check that only necessary state_bounds are defined
    expected_keys = ["flow_mol_phase_comp", "enth_mol",
                     "temperature", "pressure"]
    if (b.params.config.state_bounds is not None and
            any(b.params.config.state_bounds.keys()) not in expected_keys):
        for k in b.params.config.state_bounds.keys():
            if k not in expected_keys:
                raise ConfigurationError(
                    "{} - found unexpected state_bounds key {}. Please ensure "
                    "bounds are provided only for expected state variables "
                    "and that you have typed the variable names correctly."
                    .format(b.name, k))

    units = b.params.get_metadata().derived_units
    # Get bounds and initial values from config args
    f_bounds, f_init = get_bounds_from_config(
        b, "flow_mol_phase_comp", units["flow_mole"])
    t_bounds, t_init = get_bounds_from_config(
        b, "temperature", units["temperature"])
    p_bounds, p_init = get_bounds_from_config(
        b, "pressure", units["pressure"])

    # Add state variables
    b.flow_mol_phase_comp = Var(b.phase_component_set,
                                initialize=f_init,
                                domain=NonNegativeReals,
                                bounds=f_bounds,
                                doc='Phase-component molar flowrate',
                                units=units["flow_mole"])
    b.pressure = Var(initialize=p_init,
                     domain=NonNegativeReals,
                     bounds=p_bounds,
                     doc='State pressure',
                     units=units["pressure"])
    b.temperature = Var(initialize=t_init,
                        domain=NonNegativeReals,
                        bounds=t_bounds,
                        doc='State temperature',
                        units=units["temperature"])

    # Add supporting variables
    b.flow_mol = Expression(
        expr=sum(b.flow_mol_phase_comp[i]
                 for i in b.phase_component_set),
        doc="Total molar flowrate")

    def flow_mol_phase(b, p):
        return sum(b.flow_mol_phase_comp[p, j]
                   for j in b.component_list
                   if (p, j) in b.phase_component_set)
    b.flow_mol_phase = Expression(b.phase_list,
                                  rule=flow_mol_phase,
                                  doc='Phase molar flow rates')

    def rule_flow_mol_comp(b, j):
        return sum(b.flow_mol_phase_comp[p, j]
                   for p in b.phase_list
                   if (p, j) in b.phase_component_set)
    b.flow_mol_comp = Expression(b.component_list,
                                 rule=rule_flow_mol_comp,
                                 doc='Component molar flow rates')

    def mole_frac_comp(b, j):
        return (sum(b.flow_mol_phase_comp[p, j]
                    for p in b.phase_list
                    if (p, j) in b.phase_component_set) / b.flow_mol)
    b.mole_frac_comp = Expression(b.component_list,
                                  rule=mole_frac_comp,
                                  doc='Mixture mole fractions')

    b.mole_frac_phase_comp = Var(
            b.phase_component_set,
            bounds=(1e-20, 1.001),
            initialize=1/len(b.component_list),
            doc='Phase mole fractions',
            units=None)

    def rule_mole_frac_phase_comp(b, p, j):
        # Calcualting mole frac phase comp is degenerate if there is only one
        # component in phase.
        # Count components
        comp_count = 0
        for p1, j1 in b.phase_component_set:
            if p1 == p:
                comp_count += 1

        if comp_count > 1:
            return b.mole_frac_phase_comp[p, j] * b.flow_mol_phase[p] == \
                b.flow_mol_phase_comp[p, j]
        else:
            return b.mole_frac_phase_comp[p, j] == 1
    b.mole_frac_phase_comp_eq = Constraint(
        b.phase_component_set, rule=rule_mole_frac_phase_comp)

    def rule_phase_frac(b, p):
        if len(b.phase_list) == 1:
            return 1
        else:
            return b.flow_mol_phase[p] / b.flow_mol
    b.phase_frac = Expression(
        b.phase_list,
        rule=rule_phase_frac,
        doc='Phase fractions')

    # Add electrolye state vars if required
    if b.params._electrolyte:
        define_electrolyte_state(b)

    # -------------------------------------------------------------------------
    # General Methods
    def get_material_flow_terms_FpcTP(p, j):
        """Create material flow terms for control volume."""
        return b.flow_mol_phase_comp[p, j]
    b.get_material_flow_terms = get_material_flow_terms_FpcTP

    def get_enthalpy_flow_terms_FpcTP(p):
        """Create enthalpy flow terms."""
        # enth_mol_phase probably does not exist when this is created
        # Use try/except to build flow term if not present
        try:
            eflow = b._enthalpy_flow_term
        except AttributeError:
            def rule_eflow(b, p):
                return b.flow_mol_phase[p] * b.enth_mol_phase[p]
            eflow = b._enthalpy_flow_term = Expression(
                b.phase_list, rule=rule_eflow)
        return eflow[p]
    b.get_enthalpy_flow_terms = get_enthalpy_flow_terms_FpcTP

    def get_material_density_terms_FpcTP(p, j):
        """Create material density terms."""
        # dens_mol_phase probably does not exist when this is created
        # Use try/except to build term if not present
        try:
            mdens = b._material_density_term
        except AttributeError:
            def rule_mdens(b, p, j):
                return b.dens_mol_phase[p] * b.mole_frac_phase_comp[p, j]
            mdens = b._material_density_term = Expression(
                b.phase_component_set, rule=rule_mdens)
        return mdens[p, j]
    b.get_material_density_terms = get_material_density_terms_FpcTP

    def get_energy_density_terms_FpcTP(p):
        """Create energy density terms."""
        # Density and energy terms probably do not exist when this is created
        # Use try/except to build term if not present
        try:
            edens = b._energy_density_term
        except AttributeError:
            def rule_edens(b, p):
                return b.dens_mol_phase[p] * b.energy_internal_mol_phase[p]
            edens = b._energy_density_term = Expression(
                b.phase_list, rule=rule_edens)
        return edens[p]
    b.get_energy_density_terms = get_energy_density_terms_FpcTP

    def default_material_balance_type_FpcTP():
        return MaterialBalanceType.componentTotal
    b.default_material_balance_type = default_material_balance_type_FpcTP

    def default_energy_balance_type_FpcTP():
        return EnergyBalanceType.enthalpyTotal
    b.default_energy_balance_type = default_energy_balance_type_FpcTP

    def get_material_flow_basis_FpcTP():
        return MaterialFlowBasis.molar
    b.get_material_flow_basis = get_material_flow_basis_FpcTP

    def define_state_vars_FpcTP():
        """Define state vars."""
        return {"flow_mol_phase_comp": b.flow_mol_phase_comp,
                "temperature": b.temperature,
                "pressure": b.pressure}
    b.define_state_vars = define_state_vars_FpcTP

    def define_display_vars_FpcTP():
        """Define display vars."""
        return {"Molar Flowrate": b.flow_mol_phase_comp,
                "Temperature": b.temperature,
                "Pressure": b.pressure}
    b.define_display_vars = define_display_vars_FpcTP
예제 #27
0
def homotopy(model,
             variables,
             targets,
             max_solver_iterations=50,
             max_solver_time=10,
             step_init=0.1,
             step_cut=0.5,
             iter_target=4,
             step_accel=0.5,
             max_step=1,
             min_step=0.05,
             max_eval=200):
    """
    Homotopy meta-solver routine using Ipopt as the non-linear solver. This
    routine takes a model along with a list of fixed variables in that model
    and a list of target values for those variables. The routine then tries to
    iteratively move the values of the fixed variables to their target values
    using an adaptive step size.

    Args:
        model : model to be solved
        variables : list of Pyomo Var objects to be varied using homotopy.
                    Variables must be fixed.
        targets : list of target values for each variable
        max_solver_iterations : maximum number of solver iterations per
                    homotopy step (default=50)
        max_solver_time : maximum cpu time for the solver per homotopy step
                    (default=10)
        step_init : initial homotopy step size (default=0.1)
        step_cut : factor by which to reduce step size on failed step
                    (default=0.5)
        step_accel : acceleration factor for adjusting step size on successful
                     step (default=0.5)
        iter_target : target number of solver iterations per homotopy step
                    (default=4)
        max_step : maximum homotopy step size (default=1)
        min_step : minimum homotopy step size (default=0.05)
        max_eval : maximum number of homotopy evaluations (both successful and
                   unsuccessful) (default=200)

    Returns:
        Termination Condition : A Pyomo TerminationCondition Enum indicating
            how the meta-solver terminated (see documentation)
        Solver Progress : a fraction indication how far the solver progressed
            from the initial values to the target values
        Number of Iterations : number of homotopy evaluations before solver
            terminated
    """
    eps = 1e-3  # Tolerance for homotopy step convergence to 1

    # Get model logger
    _log = logging.getLogger(__name__)

    # Validate model is an instance of Block
    if not isinstance(model, Block):
        raise TypeError("Model provided was not a valid Pyomo model object "
                        "(instance of Block). Please provide a valid model.")
    if degrees_of_freedom(model) != 0:
        raise ConfigurationError(
            "Degrees of freedom in model are not equal to zero. Homotopy "
            "should not be used on probelms which are not well-defined.")

    # Validate variables and targets
    if len(variables) != len(targets):
        raise ConfigurationError(
            "Number of variables and targets do not match.")
    for i in range(len(variables)):
        v = variables[i]
        t = targets[i]

        if not isinstance(v, _VarData):
            raise TypeError("Variable provided ({}) was not a valid Pyomo Var "
                            "component.".format(v))

        # Check that v is part of model
        parent = v.parent_block()
        while parent != model:
            if parent is None:
                raise ConfigurationError(
                    "Variable {} is not part of model".format(v))
            parent = parent.parent_block()

        # Check that v is fixed
        if not v.fixed:
            raise ConfigurationError(
                "Homotopy metasolver provided with unfixed variable {}."
                "All variables must be fixed.".format(v.name))

        # Check bounds on v (they don't really matter, but check for sanity)
        if v.ub is not None:
            if v.value > v.ub:
                raise ConfigurationError(
                    "Current value for variable {} is greater than the "
                    "upper bound for that variable. Please correct this "
                    "before continuing.".format(v.name))
            if t > v.ub:
                raise ConfigurationError(
                    "Target value for variable {} is greater than the "
                    "upper bound for that variable. Please correct this "
                    "before continuing.".format(v.name))
        if v.lb is not None:
            if v.value < v.lb:
                raise ConfigurationError(
                    "Current value for variable {} is less than the "
                    "lower bound for that variable. Please correct this "
                    "before continuing.".format(v.name))
            if t < v.lb:
                raise ConfigurationError(
                    "Target value for variable {} is less than the "
                    "lower bound for that variable. Please correct this "
                    "before continuing.".format(v.name))

    # TODO : Should we be more restrictive on these values to avoid users
    # TODO : picking numbers that are less likely to solve (but still valid)?
    # Validate homotopy parameter selections
    if not 0.05 <= step_init <= 0.8:
        raise ConfigurationError("Invalid value for step_init ({}). Must lie "
                                 "between 0.05 and 0.8.".format(step_init))
    if not 0.1 <= step_cut <= 0.9:
        raise ConfigurationError("Invalid value for step_cut ({}). Must lie "
                                 "between 0.1 and 0.9.".format(step_cut))
    if step_accel < 0:
        raise ConfigurationError(
            "Invalid value for step_accel ({}). Must be "
            "greater than or equal to 0.".format(step_accel))
    if iter_target < 1:
        raise ConfigurationError(
            "Invalid value for iter_target ({}). Must be "
            "greater than or equal to 1.".format(iter_target))
    if not isinstance(iter_target, int):
        raise ConfigurationError("Invalid value for iter_target ({}). Must be "
                                 "an an integer.".format(iter_target))
    if not 0.05 <= max_step <= 1:
        raise ConfigurationError("Invalid value for max_step ({}). Must lie "
                                 "between 0.05 and 1.".format(max_step))
    if not 0.01 <= min_step <= 0.1:
        raise ConfigurationError("Invalid value for min_step ({}). Must lie "
                                 "between 0.01 and 0.1.".format(min_step))
    if not min_step <= max_step:
        raise ConfigurationError("Invalid argumnets: step_min must be less "
                                 "or equal to step_max.")
    if not min_step <= step_init <= max_step:
        raise ConfigurationError("Invalid arguments: step_init must lie "
                                 "between min_step and max_step.")
    if max_eval < 1:
        raise ConfigurationError(
            "Invalid value for max_eval ({}). Must be "
            "greater than or equal to 1.".format(step_accel))
    if not isinstance(max_eval, int):
        raise ConfigurationError("Invalid value for max_eval ({}). Must be "
                                 "an an integer.".format(iter_target))

    # Create solver object
    solver_obj = SolverFactory('ipopt')

    # Perform initial solve of model to confirm feasible initial solution
    results, solved, sol_iter, sol_time, sol_reg = ipopt_solve_with_stats(
        model, solver_obj, max_solver_iterations, max_solver_time)

    if not solved:
        _log.exception("Homotopy Failed - initial solution infeasible.")
        return TerminationCondition.infeasible, 0, 0
    elif sol_reg != "-":
        _log.warning(
            "Homotopy - initial solution converged with regularization.")
        return TerminationCondition.other, 0, 0
    else:
        _log.info("Homotopy - initial point converged")

    # Set up homotopy variables
    # Get initial values and deltas for all variables
    v_init = []
    for i in range(len(variables)):
        v_init.append(variables[i].value)

    n_0 = 0.0  # Homotopy progress variable
    s = step_init  # Set step size to step_init
    iter_count = 0  # Counter for homotopy iterations

    # Save model state to dict
    # TODO : for very large models, it may be necessary to dump this to a file
    current_state = to_json(model, return_dict=True)

    while n_0 < 1.0:
        iter_count += 1  # Increase iter_count regardless of success or failure

        # Calculate next n value given current step size
        if n_0 + s >= 1.0 - eps:
            n_1 = 1.0
        else:
            n_1 = n_0 + s

        _log.info("Homotopy Iteration {}. Next Step: {} (Current: {})".format(
            iter_count, n_1, n_0))

        # Update values for all variables using n_1
        for i in range(len(variables)):
            variables[i].fix(targets[i] * n_1 + v_init[i] * (1 - n_1))

        # Solve model at new state
        results, solved, sol_iter, sol_time, sol_reg = ipopt_solve_with_stats(
            model, solver_obj, max_solver_iterations, max_solver_time)

        # Check solver output for convergence
        if solved:
            # Step succeeded - accept current state
            current_state = to_json(model, return_dict=True)

            # Update n_0 to accept current step
            n_0 = n_1

            # Check solver iterations and calculate next step size
            s_proposed = s * (1 + step_accel * (iter_target / sol_iter - 1))

            if s_proposed > max_step:
                s = max_step
            elif s_proposed < min_step:
                s = min_step
            else:
                s = s_proposed
        else:
            # Step failed - reload old state
            from_json(model, current_state)

            # Try to cut back step size
            if s > min_step:
                # Step size can be cut
                s = max(min_step, s * step_cut)
            else:
                # Step is already at minimum size, terminate homotopy
                _log.exception(
                    "Homotopy failed - could not converge at minimum step "
                    "size. Current progress is {}".format(n_0))
                return TerminationCondition.minStepLength, n_0, iter_count

        if iter_count >= max_eval:  # Use greater than or equal to to be safe
            _log.exception("Homotopy failed - maximum homotopy iterations "
                           "exceeded. Current progress is {}".format(n_0))
            return TerminationCondition.maxEvaluations, n_0, iter_count

    if sol_reg == "-":
        _log.info("Homotopy successful - converged at target values in {} "
                  "iterations.".format(iter_count))
        return TerminationCondition.optimal, n_0, iter_count
    else:
        _log.exception("Homotopy failed - converged at target values with "
                       "regularization in {} iterations.".format(iter_count))
        return TerminationCondition.other, n_0, iter_count
예제 #28
0
    def build(self):
        """
        General build method for MixerData. This method calls a number
        of sub-methods which automate the construction of expected attributes
        of unit models.

        Inheriting models should call `super().build`.

        Args:
            None

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

        # Call setup methods from ControlVolumeBlockData
        self._get_property_package()
        self._get_indexing_sets()

        # Create list of inlet names
        inlet_list = self.create_inlet_list()

        # Build StateBlocks
        inlet_blocks = self.add_inlet_state_blocks(inlet_list)

        if self.config.mixed_state_block is None:
            mixed_block = self.add_mixed_state_block()
        else:
            mixed_block = self.get_mixed_state_block()

        mb_type = self.config.material_balance_type
        if mb_type == MaterialBalanceType.useDefault:
            t_ref = self.flowsheet().config.time.first()
            mb_type = mixed_block[t_ref].default_material_balance_type()

        if mb_type != MaterialBalanceType.none:
            self.add_material_mixing_equations(inlet_blocks=inlet_blocks,
                                               mixed_block=mixed_block,
                                               mb_type=mb_type)
        else:
            raise BurntToast("{} received unrecognised value for "
                             "material_mixing_type argument. This "
                             "should not occur, so please contact "
                             "the IDAES developers with this bug.".format(
                                 self.name))

        if self.config.energy_mixing_type == MixingType.extensive:
            self.add_energy_mixing_equations(inlet_blocks=inlet_blocks,
                                             mixed_block=mixed_block)
        elif self.config.energy_mixing_type == MixingType.none:
            pass
        else:
            raise ConfigurationError(
                "{} received unrecognised value for "
                "material_mixing_type argument. This "
                "should not occur, so please contact "
                "the IDAES developers with this bug.".format(self.name))

        # Add to try/expect to catch cases where pressure is not supported
        # by properties.
        try:
            if self.config.momentum_mixing_type == MomentumMixingType.minimize:
                self.add_pressure_minimization_equations(
                    inlet_blocks=inlet_blocks, mixed_block=mixed_block)
            elif (self.config.momentum_mixing_type ==
                  MomentumMixingType.equality):
                self.add_pressure_equality_equations(inlet_blocks=inlet_blocks,
                                                     mixed_block=mixed_block)
            elif (self.config.momentum_mixing_type ==
                  MomentumMixingType.minimize_and_equality):
                self.add_pressure_minimization_equations(
                    inlet_blocks=inlet_blocks, mixed_block=mixed_block)
                self.add_pressure_equality_equations(inlet_blocks=inlet_blocks,
                                                     mixed_block=mixed_block)
                self.pressure_equality_constraints.deactivate()
            elif self.config.momentum_mixing_type == MomentumMixingType.none:
                pass
            else:
                raise ConfigurationError(
                    "{} recieved unrecognised value for "
                    "momentum_mixing_type argument. This "
                    "should not occur, so please contact "
                    "the IDAES developers with this bug.".format(self.name))
        except PropertyNotSupportedError:
            raise PropertyNotSupportedError(
                "{} The property package supplied for this unit does not "
                "appear to support pressure, which is required for momentum "
                "mixing. Please set momentum_mixing_type to "
                "MomentumMixingType.none or provide a property package which "
                "supports pressure.".format(self.name))

        self.add_port_objects(inlet_list, inlet_blocks, mixed_block)
예제 #29
0
def define_state(b):
    # FTPx formulation always requires a flash, so set flag to True
    # TODO: should have some checking to make sure developers implement this properly
    b.always_flash = True

    # Check that only necessary state_bounds are defined
    expected_keys = ["flow_mol", "enth_mol", "temperature", "pressure"]
    if (b.params.config.state_bounds is not None
            and any(b.params.config.state_bounds.keys()) not in expected_keys):
        for k in b.params.config.state_bounds.keys():
            if "mole_frac" in k:
                _log.warning("{} - found state_bounds argument for {}."
                             " Mole fraction bounds are set automatically and "
                             "this argument will be ignored.".format(
                                 b.name, k))
            elif k not in expected_keys:
                raise ConfigurationError(
                    "{} - found unexpected state_bounds key {}. Please ensure "
                    "bounds are provided only for expected state variables "
                    "and that you have typed the variable names correctly.".
                    format(b.name, k))

    units = b.params.get_metadata().derived_units
    # Get bounds and initial values from config args
    f_bounds, f_init = get_bounds_from_config(b, "flow_mol",
                                              units["flow_mole"])
    h_bounds, h_init = get_bounds_from_config(b, "enth_mol",
                                              units["energy_mole"])
    p_bounds, p_init = get_bounds_from_config(b, "pressure", units["pressure"])
    t_bounds, t_init = get_bounds_from_config(b, "temperature",
                                              units["temperature"])

    # Add state variables
    b.flow_mol = Var(initialize=f_init,
                     domain=NonNegativeReals,
                     bounds=f_bounds,
                     doc=' Total molar flowrate',
                     units=units["flow_mole"])
    b.mole_frac_comp = Var(b.component_list,
                           bounds=(0, None),
                           initialize=1 / len(b.component_list),
                           doc='Mixture mole fractions',
                           units=None)
    b.pressure = Var(initialize=p_init,
                     domain=NonNegativeReals,
                     bounds=p_bounds,
                     doc='State pressure',
                     units=units["pressure"])

    b.enth_mol = Var(initialize=h_init,
                     bounds=h_bounds,
                     doc='State molar enthalpy',
                     units=units["energy_mole"])

    # Add supporting variables
    if f_init is None:
        fp_init = None
    else:
        fp_init = f_init / len(b.phase_list)

    b.flow_mol_phase = Var(b.phase_list,
                           initialize=fp_init,
                           domain=NonNegativeReals,
                           bounds=f_bounds,
                           doc='Phase molar flow rates',
                           units=units["flow_mole"])

    b.mole_frac_phase_comp = Var(b.phase_component_set,
                                 initialize=1 / len(b.component_list),
                                 bounds=(0, None),
                                 doc='Phase mole fractions',
                                 units=None)

    b.temperature = Var(initialize=t_init,
                        domain=NonNegativeReals,
                        bounds=t_bounds,
                        doc='Temperature',
                        units=units["temperature"])

    b.phase_frac = Var(b.phase_list,
                       initialize=1 / len(b.phase_list),
                       bounds=(0, None),
                       doc='Phase fractions',
                       units=None)

    # Add supporting constraints
    if b.config.defined_state is False:
        # applied at outlet only
        b.sum_mole_frac_out = Constraint(expr=1e3 == 1e3 *
                                         sum(b.mole_frac_comp[i]
                                             for i in b.component_list))

    def rule_enth_mol(b):
        return b.enth_mol == sum(b.enth_mol_phase[p] * b.phase_frac[p]
                                 for p in b.phase_list)

    b.enth_mol_eq = Constraint(rule=rule_enth_mol,
                               doc="Total molar enthalpy mixing rule")

    if len(b.phase_list) == 1:

        def rule_total_mass_balance(b):
            return b.flow_mol_phase[b.phase_list[1]] == b.flow_mol

        b.total_flow_balance = Constraint(rule=rule_total_mass_balance)

        def rule_comp_mass_balance(b, i):
            return 1e3*b.mole_frac_comp[i] == \
                1e3*b.mole_frac_phase_comp[b.phase_list[1], i]

        b.component_flow_balances = Constraint(b.component_list,
                                               rule=rule_comp_mass_balance)

        def rule_phase_frac(b, p):
            return b.phase_frac[p] == 1

        b.phase_fraction_constraint = Constraint(b.phase_list,
                                                 rule=rule_phase_frac)

    elif len(b.phase_list) == 2:
        # For two phase, use Rachford-Rice formulation
        def rule_total_mass_balance(b):
            return sum(b.flow_mol_phase[p] for p in b.phase_list) == \
                b.flow_mol

        b.total_flow_balance = Constraint(rule=rule_total_mass_balance)

        def rule_comp_mass_balance(b, i):
            return b.flow_mol * b.mole_frac_comp[i] == sum(
                b.flow_mol_phase[p] * b.mole_frac_phase_comp[p, i]
                for p in b.phase_list if (p, i) in b.phase_component_set)

        b.component_flow_balances = Constraint(b.component_list,
                                               rule=rule_comp_mass_balance)

        def rule_mole_frac(b):
            return 1e3*sum(b.mole_frac_phase_comp[b.phase_list[1], i]
                           for i in b.component_list
                           if (b.phase_list[1], i)
                           in b.phase_component_set) -\
                1e3*sum(b.mole_frac_phase_comp[b.phase_list[2], i]
                        for i in b.component_list
                        if (b.phase_list[2], i)
                        in b.phase_component_set) == 0

        b.sum_mole_frac = Constraint(rule=rule_mole_frac)

        def rule_phase_frac(b, p):
            return b.phase_frac[p] * b.flow_mol == b.flow_mol_phase[p]

        b.phase_fraction_constraint = Constraint(b.phase_list,
                                                 rule=rule_phase_frac)

    else:
        # Otherwise use a general formulation
        def rule_comp_mass_balance(b, i):
            return b.flow_mol * b.mole_frac_comp[i] == sum(
                b.flow_mol_phase[p] * b.mole_frac_phase_comp[p, i]
                for p in b.phase_list if (p, i) in b.phase_component_set)

        b.component_flow_balances = Constraint(b.component_list,
                                               rule=rule_comp_mass_balance)

        def rule_mole_frac(b, p):
            return 1e3 * sum(b.mole_frac_phase_comp[p, i]
                             for i in b.component_list
                             if (p, i) in b.phase_component_set) == 1e3

        b.sum_mole_frac = Constraint(b.phase_list, rule=rule_mole_frac)

        def rule_phase_frac(b, p):
            return b.phase_frac[p] * b.flow_mol == b.flow_mol_phase[p]

        b.phase_fraction_constraint = Constraint(b.phase_list,
                                                 rule=rule_phase_frac)

    # -------------------------------------------------------------------------
    # General Methods
    def get_material_flow_terms_FTPx(p, j):
        """Create material flow terms for control volume."""
        if j in b.component_list:
            return b.flow_mol_phase[p] * b.mole_frac_phase_comp[p, j]
        else:
            return 0

    b.get_material_flow_terms = get_material_flow_terms_FTPx

    def get_enthalpy_flow_terms_FTPx(p):
        """Create enthalpy flow terms."""
        return b.flow_mol_phase[p] * b.enth_mol_phase[p]

    b.get_enthalpy_flow_terms = get_enthalpy_flow_terms_FTPx

    def get_material_density_terms_FTPx(p, j):
        """Create material density terms."""
        if j in b.component_list:
            return b.dens_mol_phase[p] * b.mole_frac_phase_comp[p, j]
        else:
            return 0

    b.get_material_density_terms = get_material_density_terms_FTPx

    def get_energy_density_terms_FTPx(p):
        """Create energy density terms."""
        return b.dens_mol_phase[p] * b.enth_mol_phase[p]

    b.get_energy_density_terms = get_energy_density_terms_FTPx

    def default_material_balance_type_FTPx():
        return MaterialBalanceType.componentTotal

    b.default_material_balance_type = default_material_balance_type_FTPx

    def default_energy_balance_type_FTPx():
        return EnergyBalanceType.enthalpyTotal

    b.default_energy_balance_type = default_energy_balance_type_FTPx

    def get_material_flow_basis_FTPx():
        return MaterialFlowBasis.molar

    b.get_material_flow_basis = get_material_flow_basis_FTPx

    def define_state_vars_FPhx():
        """Define state vars."""
        return {
            "flow_mol": b.flow_mol,
            "mole_frac_comp": b.mole_frac_comp,
            "enth_mol": b.enth_mol,
            "pressure": b.pressure
        }

    b.define_state_vars = define_state_vars_FPhx

    def define_display_vars_FPhx():
        """Define display vars."""
        return {
            "Total Molar Flowrate": b.flow_mol,
            "Total Mole Fraction": b.mole_frac_comp,
            "Molar Enthalpy": b.enth_mol,
            "Pressure": b.pressure
        }

    b.define_display_vars = define_display_vars_FPhx
예제 #30
0
    def initialize(blk,
                   liquid_state_args=None,
                   vapor_state_args=None,
                   outlvl=idaeslog.NOTSET,
                   solver=None,
                   optarg=None):
        '''
        Initialization routine for solvent condenser unit model.

        Keyword Arguments:
            liquid_state_args : a dict of arguments to be passed to the
                liquid property package to provide an initial state for
                initialization (see documentation of the specific property
                package) (default = none).
            vapor_state_args : a dict of arguments to be passed to the
                vapor property package to provide an initial state for
                initialization (see documentation of the specific property
                package) (default = none).
            outlvl : sets output level of initialization routine
            optarg : solver options dictionary object (default=None, use
                     default solver options)
            solver : str indicating which solver to use during
                     initialization (default = None, use default IDAES solver)

        Returns:
            None
        '''
        if optarg is None:
            optarg = {}

        # Check DOF
        if degrees_of_freedom(blk) != 0:
            raise ConfigurationError(
                f"{blk.name} degrees of freedom were not 0 at the beginning "
                f"of initialization. DoF = {degrees_of_freedom(blk)}")

        # Set solver options
        init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit")

        solverobj = get_solver(solver, optarg)

        # ---------------------------------------------------------------------
        # Initialize liquid phase control volume block
        flags = blk.vapor_phase.initialize(outlvl=outlvl,
                                           optarg=optarg,
                                           solver=solver,
                                           state_args=vapor_state_args,
                                           hold_state=True)

        init_log.info_high('Initialization Step 1 Complete.')
        # ---------------------------------------------------------------------
        # Initialize liquid phase state block
        if liquid_state_args is None:
            t_init = blk.flowsheet().time.first()
            liquid_state_args = {}
            liq_state_vars = blk.liquid_phase[t_init].define_state_vars()

            vap_state = blk.vapor_phase.properties_out[t_init]

            # Check for unindexed state variables
            for sv in liq_state_vars:
                if "flow" in sv:
                    # Flow varaible, assume 10% condensation
                    if "phase_comp" in sv:
                        # Flow is indexed by phase and component
                        liquid_state_args[sv] = {}
                        for p, j in liq_state_vars[sv]:
                            if j in vap_state.component_list:
                                liquid_state_args[sv][p, j] = 0.1 * value(
                                    getattr(vap_state, sv)[p, j])
                            else:
                                liquid_state_args[sv][p, j] = 1e-8
                    elif "comp" in sv:
                        # Flow is indexed by component
                        liquid_state_args[sv] = {}
                        for j in liq_state_vars[sv]:
                            if j in vap_state.component_list:
                                liquid_state_args[sv][j] = 0.1 * value(
                                    getattr(vap_state, sv)[j])
                            else:
                                liquid_state_args[sv][j] = 1e-8
                    elif "phase" in sv:
                        # Flow is indexed by phase
                        liquid_state_args[sv] = {}
                        for p in liq_state_vars[sv]:
                            liquid_state_args[sv][p] = 0.1 * value(
                                getattr(vap_state, sv)["Vap"])
                    else:
                        liquid_state_args[sv] = 0.1 * value(
                            getattr(vap_state, sv))
                elif "mole_frac" in sv:
                    liquid_state_args[sv] = {}
                    if "phase" in sv:
                        # Variable is indexed by phase and component
                        for p, j in liq_state_vars[sv].keys():
                            if j in vap_state.component_list:
                                liquid_state_args[sv][p, j] = value(
                                    vap_state.fug_phase_comp["Vap", j] /
                                    vap_state.pressure)
                            else:
                                liquid_state_args[sv][p, j] = 1e-8
                    else:
                        for j in liq_state_vars[sv].keys():
                            if j in vap_state.component_list:
                                liquid_state_args[sv][j] = value(
                                    vap_state.fug_phase_comp["Vap", j] /
                                    vap_state.pressure)
                            else:
                                liquid_state_args[sv][j] = 1e-8
                else:
                    liquid_state_args[sv] = value(getattr(vap_state, sv))

        blk.liquid_phase.initialize(outlvl=outlvl,
                                    optarg=optarg,
                                    solver=solver,
                                    state_args=liquid_state_args,
                                    hold_state=False)

        init_log.info_high('Initialization Step 2 Complete.')
        # ---------------------------------------------------------------------
        # Solve unit model
        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            results = solverobj.solve(blk, tee=slc.tee)

        init_log.info_high("Initialization Step 3 {}.".format(
            idaeslog.condition(results)))

        # ---------------------------------------------------------------------
        # Release Inlet state
        blk.vapor_phase.release_state(flags, outlvl)

        init_log.info('Initialization Complete: {}'.format(
            idaeslog.condition(results)))