コード例 #1
0
def parameters_nt_sum(cobj, prop, nlist, tlist):
    """
    Create parameters for expression forms using n-t parameters

    Args:
        cobj: Component object that will contain the parameters
        prop: name of property parameters are associated with
        nlist: list of values for n-parameter
        tlist: list of values for t-parameter

    Returns:
        None
    """
    if len(nlist) != len(tlist):
        raise ConfigurationError(
            f"{cobj.name} mismatched length between n and t parameters "
            f"for CoolProp exponential form for property {prop}. Please "
            f"ensure the number of n and t parameters are equal.")

    # Use multiple Vars, instead of single indexed Var, to have same
    # structure as cases where each parameter value has different units

    for i, nval in enumerate(nlist):
        coeff = Var(doc="Multiplying parameter for CoolProp exponential form",
                    units=pyunits.dimensionless)
        cobj.add_component(prop+"_coeff_n"+str(i+1), coeff)
        coeff.fix(nval)

    for i, tval in enumerate(tlist):
        coeff = Var(doc="Exponent parameter for CoolProp exponential form",
                    units=pyunits.dimensionless)
        cobj.add_component(prop+"_coeff_t"+str(i+1), coeff)
        coeff.fix(tval)
コード例 #2
0
class FWHCondensing0DData(HeatExchangerData):
    def build(self):
        super().build()
        units_meta = self.shell.config.property_package.get_metadata()
        self.enth_sub = Var(self.flowsheet().time,
                            initialize=0,
                            units=units_meta.get_derived_units("energy_mole"))
        self.enth_sub.fix()

        @self.Constraint(
            self.flowsheet().time,
            doc="Calculate steam extraction rate such that all steam condenses",
        )
        def extraction_rate_constraint(b, t):
            return (b.shell.properties_out[t].enth_mol - b.enth_sub[t] ==
                    b.shell.properties_out[t].enth_mol_sat_phase["Liq"])

    def initialize(self, *args, **kwargs):
        """
        Use the regular heat exchanger initialization, with the extraction rate
        constraint deactivated; then it activates the constraint and calculates
        a steam inlet flow rate.
        """
        solver = kwargs.get("solver", None)
        optarg = kwargs.get("oparg", {})
        outlvl = kwargs.get("outlvl", idaeslog.NOTSET)
        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")

        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)

        self.extraction_rate_constraint.deactivate()
        self.area.fix()
        self.overall_heat_transfer_coefficient.fix()
        self.inlet_1.fix()
        self.inlet_2.fix()
        self.outlet_1.unfix()
        self.outlet_2.unfix()

        # Do regular heat exchanger intialization
        super().initialize(*args, **kwargs)
        self.extraction_rate_constraint.activate()
        self.inlet_1.flow_mol.unfix()

        # Create solver
        opt = get_solver(solver, optarg)

        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = opt.solve(self, tee=slc.tee)
        init_log.info(
            "Initialization Complete (w/ extraction calc): {}".format(
                idaeslog.condition(res)))

        from_json(self, sd=istate, wts=sp)
コード例 #3
0
class HelmTurbineStageData(HelmIsentropicTurbineData):
    CONFIG = HelmIsentropicTurbineData.CONFIG()

    def build(self):
        super().build()

        self.efficiency_mech = Var(initialize=1.0,
                                   doc="Turbine mechanical efficiency")
        self.efficiency_mech.fix()
        time_set = self.flowsheet().config.time
        self.shaft_speed = Var(time_set,
                               doc="Shaft speed [1/s]",
                               initialize=60.0)
        self.shaft_speed.fix()

        @self.Expression(time_set, doc="Specific speed [dimensionless]")
        def specific_speed(b, t):
            s = b.shaft_speed[t]  # 1/s
            v = b.control_volume.properties_out[t].flow_vol  # m3/s
            his_rate = b.work_isentropic[t]  # J/s
            m = b.control_volume.properties_out[t].flow_mass  # kg/s
            return s * v**0.5 * (his_rate / m)**(-0.75)  # dimensionless

        @self.Expression(time_set, doc="Thermodynamic power [J/s]")
        def power_thermo(b, t):
            return b.control_volume.work[t]

        @self.Expression(self.flowsheet().config.time, doc="Shaft power [J/s]")
        def power_shaft(b, t):
            return b.power_thermo[t] * b.efficiency_mech

    def initialize(
        self,
        outlvl=idaeslog.NOTSET,
        solver="ipopt",
        optarg={
            "tol": 1e-6,
            "max_iter": 30
        },
    ):
        """
        Initialize the turbine stage model.  This deactivates the
        specialized constraints, then does the isentropic turbine initialization,
        then reactivates the constraints and solves.

        Args:
            outlvl : sets output level of initialization routine
            solver (str): Solver to use for initialization
            optarg (dict): Solver arguments dictionary
        """
        super().initialize(outlvl=outlvl, solver=solver, optarg=optarg)

    def calculate_scaling_factors(self):
        super().calculate_scaling_factors()
コード例 #4
0
class FWHCondensing0DData(HeatExchangerData):
    def build(self):
        super().build()
        self.enth_sub = Var(self.flowsheet().config.time, initialize=0)
        self.enth_sub.fix()
        @self.Constraint(self.flowsheet().config.time,
            doc="Calculate steam extraction rate such that all steam condenses")
        def extraction_rate_constraint(b, t):
            return  b.shell.properties_out[t].enth_mol - b.enth_sub[t] == \
                   b.shell.properties_out[t].enth_mol_sat_phase["Liq"]

    def initialize(self, *args, **kwargs):
        """
        Use the regular heat exchanger initilization, with the extraction rate
        constraint deactivated; then it activates the constraint and calculates
        a steam inlet flow rate.
        """
        self.extraction_rate_constraint.deactivate()
        super().initialize(*args, **kwargs)
        self.extraction_rate_constraint.activate()

        solver = kwargs.get("solver", "ipopt")
        optarg = kwargs.get("oparg", {})
        outlvl = kwargs.get("outlvl", 0)

        opt = SolverFactory(solver)
        opt.options = optarg
        tee = True if outlvl >= 3 else False
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)

        self.area.fix()
        self.overall_heat_transfer_coefficient.fix()
        self.inlet_1.fix()
        self.inlet_2.fix()
        self.outlet_1.unfix()
        self.outlet_2.unfix()
        self.inlet_1.flow_mol.unfix()
        results = opt.solve(self, tee=tee)

        if results.solver.termination_condition == TerminationCondition.optimal:
            if outlvl >= 2:
                _log.info('{} Initialization Failed.'.format(self.name))
        else:
            _log.warning('{} Initialization Failed.'.format(self.name))

        from_json(self, sd=istate, wts=sp)
コード例 #5
0
        def build_parameters(cobj):
            cname = cobj.local_name
            cdict = CoolPropWrapper._get_component_data(cname)

            # 29-Dec-21: CoolProp only uses rational_polynomial.
            Alist, Blist = CoolPropWrapper._get_param_dicts(
                cname, cdict, "enth_mol_liq_comp", "hL",
                ["rational_polynomial"])
            href = cdict["EOS"][0]["STATES"]["hs_anchor"]["hmolar"]

            cforms.parameters_polynomial(cobj, "enth_mol_liq_comp",
                                         pyunits.J / pyunits.mol, Alist, Blist)

            href_var = Var(doc="Reference heat of formation",
                           units=pyunits.J / pyunits.mol)
            cobj.add_component("enth_mol_liq_comp_anchor", href_var)
            href_var.fix(href)
コード例 #6
0
def parameters_polynomial(cobj, prop, prop_units, alist, blist):
    """
    Create parameters for expression forms using A-B parameters (rational
    polynomial forms)

    Args:
        cobj: Component object that will contain the parameters
        prop: name of property parameters are associated with
        prop_units: units of measurement for property
        Alist: list of values for A-parameter
        Blist: list of values for B-parameter

    Returns:
        None
    """
    for i, aval in enumerate(alist):
        if i == 0:
            param_units = prop_units
        else:
            param_units = prop_units/pyunits.K**i

        coeff = Var(doc="A parameter for CoolProp polynomial form",
                    units=param_units)
        cobj.add_component(prop+"_coeff_A"+str(i), coeff)
        coeff.fix(aval)

    for i, bval in enumerate(blist):
        if i == 0:
            param_units = pyunits.dimensionless
        else:
            param_units = pyunits.K**-i

        coeff = Var(doc="B parameter for CoolProp exponential form",
                    units=param_units)
        cobj.add_component(prop+"_coeff_B"+str(i), coeff)
        coeff.fix(bval)
コード例 #7
0
ファイル: turbine_outlet.py プロジェクト: jkreuder/idaes-pse
class TurbineOutletStageData(HelmIsentropicTurbineData):
    # Same settings as the default pressure changer, but force to expander with
    # isentropic efficiency
    CONFIG = HelmIsentropicTurbineData.CONFIG()

    def build(self):
        super().build()

        self.flow_coeff = Var(initialize=0.0333,
                              doc="Turbine flow coefficient [kg*C^0.5/s/Pa]")
        self.eff_dry = Var(initialize=0.87,
                           doc="Turbine dry isentropic efficiency")
        self.design_exhaust_flow_vol = Var(
            initialize=6000.0, doc="Design exit volumetirc flowrate [m^3/s]")
        self.efficiency_mech = Var(initialize=1.0,
                                   doc="Turbine mechanical efficiency")
        self.efficiency_isentropic.unfix()
        self.eff_dry.fix()
        self.design_exhaust_flow_vol.fix()
        self.flow_coeff.fix()
        self.efficiency_mech.fix()

        @self.Expression(self.flowsheet().config.time,
                         doc="Eff. fact. correlation")
        def tel(b, t):
            f = b.control_volume.properties_out[
                t].flow_vol / b.design_exhaust_flow_vol
            return 1e6 * (-0.0035 * f**5 + 0.022 * f**4 - 0.0542 * f**3 +
                          0.0638 * f**2 - 0.0328 * f + 0.0064)

        @self.Constraint(self.flowsheet().config.time,
                         doc="Stodola eq. choked flow")
        def stodola_equation(b, t):
            flow = b.control_volume.properties_in[t].flow_mol
            mw = b.control_volume.properties_in[t].mw
            Tin = b.control_volume.properties_in[t].temperature
            Pin = b.control_volume.properties_in[t].pressure
            Pr = b.ratioP[t]
            cf = b.flow_coeff
            return flow**2 * mw**2 * (Tin) == (cf**2 * Pin**2 * (1 - Pr**2))

        @self.Constraint(self.flowsheet().config.time,
                         doc="Efficiency correlation")
        def efficiency_correlation(b, t):
            x = b.control_volume.properties_out[t].vapor_frac
            eff = b.efficiency_isentropic[t]
            dh_isen = b.delta_enth_isentropic[t]
            tel = b.tel[t]
            return eff == b.eff_dry * x * (1 - 0.65 *
                                           (1 - x)) * (1 + tel / dh_isen)

        @self.Expression(self.flowsheet().config.time,
                         doc="Thermodynamic power [J/s]")
        def power_thermo(b, t):
            return b.control_volume.work[t]

        @self.Expression(self.flowsheet().config.time, doc="Shaft power [J/s]")
        def power_shaft(b, t):
            return b.power_thermo[t] * b.efficiency_mech

    def initialize(
        self,
        state_args={},
        outlvl=idaeslog.NOTSET,
        solver="ipopt",
        optarg={
            "tol": 1e-6,
            "max_iter": 30
        },
        calculate_cf=True,
    ):
        """
        Initialize the outlet turbine stage model.  This deactivates the
        specialized constraints, then does the isentropic turbine initialization,
        then reactivates the constraints and solves.

        Args:
            state_args (dict): Initial state for property initialization
            outlvl : sets output level of initialization routine
            solver (str): Solver to use for initialization
            optarg (dict): Solver arguments dictionary
        """
        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")

        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)

        # sp is what to save to make sure state after init is same as the start
        #   saves value, fixed, and active state, doesn't load originally free
        #   values, this makes sure original problem spec is same but initializes
        #   the values of free vars
        for t in self.flowsheet().config.time:
            if self.outlet.pressure[t].fixed:
                self.ratioP[t] = value(self.outlet.pressure[t] /
                                       self.inlet.pressure[t])
                self.deltaP[t] = value(self.outlet.pressure[t] -
                                       self.inlet.pressure[t])

        # Deactivate special constraints
        self.stodola_equation.deactivate()
        self.efficiency_correlation.deactivate()
        self.efficiency_isentropic.fix()
        self.deltaP.unfix()
        self.ratioP.unfix()
        self.inlet.fix()
        self.outlet.unfix()

        super().initialize(outlvl=outlvl, solver=solver, optarg=optarg)

        for t in self.flowsheet().config.time:
            mw = self.control_volume.properties_in[t].mw
            Tin = self.control_volume.properties_in[t].temperature
            Pin = self.control_volume.properties_in[t].pressure
            Pr = self.ratioP[t]
            if not calculate_cf:
                cf = self.flow_coeff
                self.inlet.flow_mol[t].fix(
                    value(cf * Pin * sqrt(1 - Pr**2) / mw / sqrt(Tin)))

        super().initialize(outlvl=outlvl, solver=solver, optarg=optarg)
        self.control_volume.properties_out[:].pressure.fix()

        # Free eff_isen and activate sepcial constarints
        self.efficiency_isentropic.unfix()
        self.outlet.pressure.fix()
        if calculate_cf:
            self.flow_coeff.unfix()
            self.inlet.flow_mol.unfix()
            self.inlet.flow_mol[0].fix()
            flow = self.control_volume.properties_in[0].flow_mol
            mw = self.control_volume.properties_in[0].mw
            Tin = self.control_volume.properties_in[0].temperature
            Pin = self.control_volume.properties_in[0].pressure
            Pr = self.ratioP[0]
            self.flow_coeff.value = value(flow * mw * sqrt(Tin / (1 - Pr**2)) /
                                          Pin)

        else:
            self.inlet.flow_mol.unfix()

        self.stodola_equation.activate()
        self.efficiency_correlation.activate()
        slvr = SolverFactory(solver)
        slvr.options = optarg
        self.display()
        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = slvr.solve(self, tee=slc.tee)
        init_log.info("Initialization Complete (Outlet Stage): {}".format(
            idaeslog.condition(res)))

        # reload original spec
        if calculate_cf:
            cf = value(self.flow_coeff)
        from_json(self, sd=istate, wts=sp)
        if calculate_cf:
            # cf was probably fixed, so will have to set the value agian here
            # if you ask for it to be calculated.
            self.flow_coeff = cf

    def calculate_scaling_factors(self):
        super().calculate_scaling_factors()
        for t, c in self.stodola_equation.items():
            s = iscale.get_scaling_factor(
                self.control_volume.properties_in[t].flow_mol,
                default=1,
                warning=True)**2
            iscale.constraint_scaling_transform(c, s)
コード例 #8
0
class PhysicalParameterData(PhysicalParameterBlock):
    """
    Property Parameter Block Class

    Contains parameters and indexing sets associated with properties for
    methane CLC.
    """
    def build(self):
        '''
        Callable method for Block construction.
        '''
        super(PhysicalParameterData, self).build()

        self._state_block_class = SolidPhaseThermoStateBlock

        # Create Phase object
        self.Sol = SolidPhase()

        # Create Component objects
        self.Fe2O3 = Component()
        self.Fe3O4 = Component()
        self.Al2O3 = Component()

        # -------------------------------------------------------------------------
        """ Pure solid component properties"""

        # Mol. weights of solid components - units = kg/mol. ref: NIST webbook
        mw_comp_dict = {'Fe2O3': 0.15969, 'Fe3O4': 0.231533, 'Al2O3': 0.10196}
        self.mw_comp = Param(
            self.component_list,
            mutable=False,
            initialize=mw_comp_dict,
            doc="Molecular weights of solid components [kg/mol]")

        # Skeletal density of solid components - units = kg/m3. ref: NIST
        dens_mass_comp_skeletal_dict = {
            'Fe2O3': 5250,
            'Fe3O4': 5000,
            'Al2O3': 3987
        }
        self.dens_mass_comp_skeletal = Param(
            self.component_list,
            mutable=False,
            initialize=dens_mass_comp_skeletal_dict,
            doc='Skeletal density of solid components'
            '[kg/m3]')

        # Ideal gas spec. heat capacity parameters(Shomate) of
        # components - ref: NIST webbook. Shomate equations from NIST.
        # Parameters A-E are used for cp calcs while A-H are used for enthalpy
        # calc.
        # 1e3*cp_comp = A + B*T + C*T^2 + D*T^3 + E/(T^2)
        # where T = Temperature (K)/1000, and cp_comp = (kJ/mol.K)
        # H_comp = H - H(298.15) = A*T + B*T^2/2 + C*T^3/3 +
        # D*T^4/4 - E/T + F - H where T = Temp (K)/1000 and H_comp = (kJ/mol)
        cp_param_dict = {
            ('Al2O3', 1): 102.4290,
            ('Al2O3', 2): 38.74980,
            ('Al2O3', 3): -15.91090,
            ('Al2O3', 4): 2.628181,
            ('Al2O3', 5): -3.007551,
            ('Al2O3', 6): -1717.930,
            ('Al2O3', 7): 146.9970,
            ('Al2O3', 8): -1675.690,
            ('Fe3O4', 1): 200.8320000,
            ('Fe3O4', 2): 1.586435e-7,
            ('Fe3O4', 3): -6.661682e-8,
            ('Fe3O4', 4): 9.452452e-9,
            ('Fe3O4', 5): 3.18602e-8,
            ('Fe3O4', 6): -1174.1350000,
            ('Fe3O4', 7): 388.0790000,
            ('Fe3O4', 8): -1120.8940000,
            ('Fe2O3', 1): 110.9362000,
            ('Fe2O3', 2): 32.0471400,
            ('Fe2O3', 3): -9.1923330,
            ('Fe2O3', 4): 0.9015060,
            ('Fe2O3', 5): 5.4336770,
            ('Fe2O3', 6): -843.1471000,
            ('Fe2O3', 7): 228.3548000,
            ('Fe2O3', 8): -825.5032000
        }
        self.cp_param = Param(self.component_list,
                              range(1, 10),
                              mutable=False,
                              initialize=cp_param_dict,
                              doc="Shomate equation heat capacity parameters")

        # Std. heat of formation of comp. - units = kJ/(mol comp) - ref: NIST
        enth_mol_form_comp_dict = {
            'Fe2O3': -825.5032,
            'Fe3O4': -1120.894,
            'Al2O3': -1675.690
        }
        self.enth_mol_form_comp = Param(
            self.component_list,
            mutable=False,
            initialize=enth_mol_form_comp_dict,
            doc="Component molar heats of formation [kJ/mol]")

        # -------------------------------------------------------------------------
        """ Mixed solid properties"""
        # These are setup as fixed vars to allow for parameter estimation

        # Particle size
        self.particle_dia = Var(domain=Reals,
                                initialize=1.5e-3,
                                doc='Diameter of solid particles [m]')
        self.particle_dia.fix()

        # TODO -provide reference
        # Minimum fluidization velocity - EPAT value used for Davidson model
        self.velocity_mf = Var(domain=Reals,
                               initialize=0.039624,
                               doc='Velocity at minimum fluidization [m/s]')
        self.velocity_mf.fix()

        # Minimum fluidization voidage - educated guess as rough
        # estimate from ergun equation results (0.4) are suspicious
        self.voidage_mf = Var(domain=Reals,
                              initialize=0.45,
                              doc='Voidage at minimum fluidization [-]')
        self.voidage_mf.fix()

        # Particle thermal conductivity
        self.therm_cond_sol = Var(domain=Reals,
                                  initialize=12.3e-3,
                                  doc='Thermal conductivity of solid'
                                  'particles [kJ/m.K.s]')
        self.therm_cond_sol.fix()

    @classmethod
    def define_metadata(cls, obj):
        obj.add_properties({
            'flow_mass': {
                'method': None,
                'units': 'kg/s'
            },
            'particle_porosity': {
                'method': None,
                'units': None
            },
            'temperature': {
                'method': None,
                'units': 'K'
            },
            'mass_frac_comp': {
                'method': None,
                'units': None
            },
            'dens_mass_skeletal': {
                'method': '_dens_mass_skeletal',
                'units': 'kg/m3'
            },
            'dens_mass_particle': {
                'method': '_dens_mass_particle',
                'units': 'kg/m3'
            },
            'cp_mol_comp': {
                'method': '_cp_mol_comp',
                'units': 'kJ/mol.K'
            },
            'cp_mass': {
                'method': '_cp_mass',
                'units': 'kJ/kg.K'
            },
            'enth_mass': {
                'method': '_enth_mass',
                'units': 'kJ/kg'
            },
            'enth_mol_comp': {
                'method': '_enth_mol_comp',
                'units': 'kJ/mol'
            }
        })

        obj.add_default_units({
            'time': 's',
            'length': 'm',
            'mass': 'kg',
            'amount': 'mol',
            'temperature': 'K',
            'energy': 'kJ',
            'holdup': 'kg'
        })
コード例 #9
0
class ReactionParameterData(ReactionParameterBlock):
    """
    Property Parameter Block Class

    Contains parameters and indexing sets associated with properties for
    superheated steam.

    """
    # Create Class ConfigBlock
    CONFIG = ConfigBlock()
    CONFIG.declare(
        "gas_property_package",
        ConfigValue(
            description="Reference to associated PropertyPackageParameter "
            "object for the gas phase.",
            domain=is_physical_parameter_block))
    CONFIG.declare(
        "solid_property_package",
        ConfigValue(
            description="Reference to associated PropertyPackageParameter "
            "object for the solid phase.",
            domain=is_physical_parameter_block))
    CONFIG.declare(
        "default_arguments",
        ConfigBlock(
            description="Default arguments to use with Property Package",
            implicit=True))

    def build(self):
        '''
        Callable method for Block construction.
        '''
        super(ReactionParameterBlock, self).build()

        self._reaction_block_class = ReactionBlock

        # Reaction Index
        self.rate_reaction_idx = Set(initialize=["R1"])

        # Reaction Stoichiometry
        self.rate_reaction_stoichiometry = {
            ("R1", "Vap", "O2"): -1,
            ("R1", "Vap", "N2"): 0,
            ("R1", "Vap", "CO2"): 0,
            ("R1", "Vap", "H2O"): 0,
            ("R1", "Sol", "Fe2O3"): 6,
            ("R1", "Sol", "Fe3O4"): -4,
            ("R1", "Sol", "Al2O3"): 0
        }

        # Standard Heat of Reaction - kJ/mol_rxn
        dh_rxn_dict = {"R1": -469.4432}
        self.dh_rxn = Param(self.rate_reaction_idx,
                            initialize=dh_rxn_dict,
                            doc="Heat of reaction [kJ/mol]",
                            units=pyunits.kJ / pyunits.mol)

        # Smoothing factor
        self.eps = Param(mutable=True, default=1e-8, doc='Smoothing Factor')
        # Reaction rate scale factor
        self._scale_factor_rxn = Param(mutable=True,
                                       default=1,
                                       doc='Scale Factor for reaction eqn.'
                                       'Used to help initialization routine')

        # -------------------------------------------------------------------------
        """ Reaction properties that can be estimated"""

        # Particle grain radius within OC particle
        self.grain_radius = Var(domain=Reals,
                                initialize=2.6e-7,
                                doc='Representative particle grain'
                                'radius within OC particle [m]',
                                units=pyunits.m)
        self.grain_radius.fix()

        # Molar density OC particle
        self.dens_mol_sol = Var(domain=Reals,
                                initialize=22472,
                                doc='Molar density of OC particle [mol/m^3]',
                                units=pyunits.mol / pyunits.m**3)
        self.dens_mol_sol.fix()

        # Available volume for reaction - from EPAT report (1-ep)'
        self.a_vol = Var(domain=Reals,
                         initialize=0.28,
                         doc='Available reaction vol. per vol. of OC',
                         units=pyunits.m**3 / pyunits.m**3)
        self.a_vol.fix()

        # Activation Energy
        self.energy_activation = Var(self.rate_reaction_idx,
                                     domain=Reals,
                                     initialize=1.4e1,
                                     doc='Activation energy [kJ/mol]',
                                     units=pyunits.kJ / pyunits.mol)
        self.energy_activation.fix()

        # Reaction order
        self.rxn_order = Var(self.rate_reaction_idx,
                             domain=Reals,
                             initialize=1.0,
                             doc='Reaction order in gas species [-]')
        self.rxn_order.fix()

        # Pre-exponential factor
        self.k0_rxn = Var(self.rate_reaction_idx,
                          domain=Reals,
                          initialize=3.1e-4,
                          doc='Pre-exponential factor'
                          '[mol^(1-N_reaction)m^(3*N_reaction -2)/s]')
        self.k0_rxn.fix()

    @classmethod
    def define_metadata(cls, obj):
        obj.add_properties({
            'k_rxn': {
                'method': '_k_rxn',
                'units': 'mol^(1-N_reaction)m^(3*N_reaction -2)/s]'
            },
            'OC_conv': {
                'method': "_OC_conv",
                'units': None
            },
            'OC_conv_temp': {
                'method': "_OC_conv_temp",
                'units': None
            },
            'reaction_rate': {
                'method': "_reaction_rate",
                'units': 'mol_rxn/m3.s'
            }
        })

        obj.add_default_units({
            'time': pyunits.s,
            'length': pyunits.m,
            'mass': pyunits.kg,
            'amount': pyunits.mol,
            'temperature': pyunits.K
        })
コード例 #10
0
ファイル: turbine_outlet.py プロジェクト: spevenhe/idaes-pse
class TurbineOutletStageData(PressureChangerData):
    # Same settings as the default pressure changer, but force to expander with
    # isentropic efficiency
    CONFIG = PressureChangerData.CONFIG()
    CONFIG.compressor = False
    CONFIG.get('compressor')._default = False
    CONFIG.get('compressor')._domain = In([False])
    CONFIG.thermodynamic_assumption = ThermodynamicAssumption.isentropic
    CONFIG.get('thermodynamic_assumption')._default = \
        ThermodynamicAssumption.isentropic
    CONFIG.get('thermodynamic_assumption')._domain = \
        In([ThermodynamicAssumption.isentropic])

    def build(self):
        super(TurbineOutletStageData, self).build()

        self.flow_coeff = Var(initialize=0.0333,
                              doc="Turbine flow coefficient [kg*C^0.5/s/Pa]")
        self.delta_enth_isentropic = Var(
            self.flowsheet().config.time,
            initialize=-100,
            doc="Specific enthalpy change of isentropic process [J/mol]")
        self.eff_dry = Var(initialize=0.87,
                           doc="Turbine dry isentropic efficiency")
        self.design_exhaust_flow_vol = Var(
            initialize=6000.0, doc="Design exit volumetirc flowrate [m^3/s]")
        self.efficiency_mech = Var(initialize=0.98,
                                   doc="Turbine mechanical efficiency")
        self.flow_scale = Param(
            mutable=True,
            default=1e3,
            doc=
            "Scaling factor for pressure flow relation should be approximatly"
            " the same order of magnitude as the expected flow.")
        self.eff_dry.fix()
        self.design_exhaust_flow_vol.fix()
        self.flow_coeff.fix()
        self.efficiency_mech.fix()
        self.ratioP[:] = 1  # make sure these have a number value
        self.deltaP[:] = 0  #   to avoid an error later in initialize

        @self.Expression(self.flowsheet().config.time,
                         doc="Efficiency factor correlation")
        def tel(b, t):
            f = b.control_volume.properties_out[
                t].flow_vol / b.design_exhaust_flow_vol
            return 1e6 * (-0.0035 * f**5 + 0.022 * f**4 - 0.0542 * f**3 +
                          0.0638 * f**2 - 0.0328 * f + 0.0064)

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: Stodola, for choked flow")
        def stodola_equation(b, t):
            flow = b.control_volume.properties_in[t].flow_mol
            mw = b.control_volume.properties_in[t].mw
            Tin = b.control_volume.properties_in[t].temperature
            Pin = b.control_volume.properties_in[t].pressure
            Pr = b.ratioP[t]
            cf = b.flow_coeff
            return (1/b.flow_scale**2)*flow**2*mw**2*(Tin - 273.15) == \
                (1/b.flow_scale**2)*cf**2*Pin**2*(1 - Pr**2)

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: isentropic specific enthalpy change")
        def isentropic_enthalpy(b, t):
            flow = b.control_volume.properties_in[t].flow_mol
            dh_isen = b.delta_enth_isentropic[t]
            work_isen = b.work_isentropic[t]
            return work_isen == dh_isen * flow

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: Efficiency correlation")
        def efficiency_correlation(b, t):
            x = b.control_volume.properties_out[t].vapor_frac
            eff = b.efficiency_isentropic[t]
            dh_isen = b.delta_enth_isentropic[t]
            tel = b.tel[t]
            return eff == b.eff_dry * x * (1 - 0.65 *
                                           (1 - x)) * (1 + tel / dh_isen)

        @self.Expression(self.flowsheet().config.time,
                         doc="Thermodynamic power [J/s]")
        def power_thermo(b, t):
            return b.control_volume.work[t]

        @self.Expression(self.flowsheet().config.time, doc="Shaft power [J/s]")
        def power_shaft(b, t):
            return b.power_thermo[t] * b.efficiency_mech

    def initialize(self,
                   state_args={},
                   outlvl=0,
                   solver='ipopt',
                   optarg={
                       'tol': 1e-6,
                       'max_iter': 30
                   }):
        """
        Initialize the outlet turbine stage model.  This deactivates the
        specialized constraints, then does the isentropic turbine initialization,
        then reactivates the constraints and solves.

        Args:
            state_args (dict): Initial state for property initialization
            outlvl (int): Amount of output (0 to 3) 0 is lowest
            solver (str): Solver to use for initialization
            optarg (dict): Solver arguments dictionary
        """
        stee = True if outlvl >= 3 else False
        # sp is what to save to make sure state after init is same as the start
        #   saves value, fixed, and active state, doesn't load originally free
        #   values, this makes sure original problem spec is same but initializes
        #   the values of free vars
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)
        # Deactivate special constraints
        self.stodola_equation.deactivate()
        self.isentropic_enthalpy.deactivate()
        self.efficiency_correlation.deactivate()
        self.deltaP.unfix()
        self.ratioP.unfix()
        # Fix turbine parameters + eff_isen
        self.eff_dry.fix()
        self.design_exhaust_flow_vol.fix()
        self.flow_coeff.fix()

        # fix inlet and free outlet
        for t in self.flowsheet().config.time:
            for k, v in self.inlet.vars.items():
                v[t].fix()
            for k, v in self.outlet.vars.items():
                v[t].unfix()
            # If there isn't a good guess for efficiency or outlet pressure
            # provide something reasonable.
            eff = self.efficiency_isentropic[t]
            eff.fix(
                eff.value if value(eff) > 0.3 and value(eff) < 1.0 else 0.8)
            # for outlet pressure try outlet pressure, pressure ratio, delta P,
            # then if none of those look reasonable use a pressure ratio of 0.8
            # to calculate outlet pressure
            Pout = self.outlet.pressure[t]
            Pin = self.inlet.pressure[t]
            prdp = value((self.deltaP[t] - Pin) / Pin)
            if value(Pout / Pin) > 0.9 or value(Pout / Pin) < 0.01:
                if value(self.ratioP[t]) < 0.9 and value(
                        self.ratioP[t]) > 0.01:
                    Pout.fix(value(Pin * self.ratioP))
                elif prdp < 0.9 and prdp > 0.01:
                    Pout.fix(value(prdp * Pin))
                else:
                    Pout.fix(value(Pin * 0.3))
            else:
                Pout.fix()
        self.deltaP[:] = value(Pout - Pin)
        self.ratioP[:] = value(Pout / Pin)

        for t in self.flowsheet().config.time:
            self.properties_isentropic[t].pressure.value = \
                value(self.outlet.pressure[t])
            self.properties_isentropic[t].flow_mol.value = \
                value(self.inlet.flow_mol[t])
            self.properties_isentropic[t].enth_mol.value = \
                value(self.inlet.enth_mol[t]*0.95)
            self.outlet.flow_mol[t].value = \
                value(self.inlet.flow_mol[t])
            self.outlet.enth_mol[t].value = \
                value(self.inlet.enth_mol[t]*0.95)

        # Make sure the initialization problem has no degrees of freedom
        # This shouldn't happen here unless there is a bug in this
        dof = degrees_of_freedom(self)
        try:
            assert (dof == 0)
        except:
            _log.exception("degrees_of_freedom = {}".format(dof))
            raise

        # one bad thing about reusing this is that the log messages aren't
        # really compatible with being nested inside another initialization
        super(TurbineOutletStageData, self).initialize(state_args=state_args,
                                                       outlvl=outlvl,
                                                       solver=solver,
                                                       optarg=optarg)

        # Free eff_isen and activate sepcial constarints
        self.efficiency_isentropic.unfix()
        self.outlet.pressure.unfix()
        self.stodola_equation.activate()
        self.isentropic_enthalpy.activate()
        self.efficiency_correlation.activate()

        slvr = SolverFactory(solver)
        slvr.options = optarg
        res = slvr.solve(self, tee=stee)

        if outlvl > 0:
            if res.solver.termination_condition == TerminationCondition.optimal:
                _log.info("{} Initialization Complete.".format(self.name))
            else:
                _log.warning(
                    """{} Initialization Failed. The most likely cause of initialization failure for
the Turbine inlet stages model is that the flow coefficient is not compatible
with flow rate guess.""".format(self.name))

        # reload original spec
        from_json(self, sd=istate, wts=sp)
コード例 #11
0
class SteamValveData(PressureChangerData):
    # Same settings as the default pressure changer, but force to expander with
    # isentropic efficiency
    CONFIG = PressureChangerData.CONFIG()
    _define_config(CONFIG)

    def build(self):
        super().build()

        self.valve_opening = Var(
            self.flowsheet().config.time,
            initialize=1,
            doc="Fraction open for valve from 0 to 1",
        )

        umeta = self.config.property_package.get_metadata().get_derived_units
        if self.config.phase == "Liq":
            cv_units = umeta("amount") / umeta("time") / umeta("pressure")**0.5
        else:
            cv_units = umeta("amount") / umeta("time") / umeta("pressure")

        self.Cv = Var(initialize=0.1,
                      doc="Valve flow coefficent",
                      units=cv_units)
        self.flow_scale = Param(
            mutable=True,
            default=1e3,
            doc="Scaling factor for pressure flow relation should be "
            "approximatly the same order of magnitude as the expected flow.",
        )
        self.Cv.fix()
        self.valve_opening.fix()

        # set up the valve function rule.  I'm not sure these matter too much
        # for us, but the options are easy enough to provide.
        if self.config.valve_function == ValveFunctionType.linear:
            rule = _linear_rule
        elif self.config.valve_function == ValveFunctionType.quick_opening:
            rule = _quick_open_rule
        elif self.config.valve_function == ValveFunctionType.equal_percentage:
            self.alpha = Var(initialize=1, doc="Valve function parameter")
            self.alpha.fix()
            rule = _equal_percentage_rule
        else:
            rule = self.config.valve_function_rule

        self.valve_function = Expression(self.flowsheet().config.time,
                                         rule=rule,
                                         doc="Valve function expression")

        if self.config.phase == "Liq":
            rule = _liquid_pressure_flow_rule
        else:
            rule = _vapor_pressure_flow_rule

        self.pressure_flow_equation = Constraint(self.flowsheet().config.time,
                                                 rule=rule)

    def initialize(
        self,
        state_args={},
        outlvl=idaeslog.NOTSET,
        solver="ipopt",
        optarg={
            "tol": 1e-6,
            "max_iter": 30
        },
    ):
        """
        Initialize the turbine stage model.  This deactivates the
        specialized constraints, then does the isentropic turbine
        initialization, then reactivates the constraints and solves.

        Args:
            state_args (dict): Initial state for property initialization
            outlvl : sets output level of initialization routine
            solver (str): Solver to use for initialization
            optarg (dict): Solver arguments dictionary
        """
        # sp is what to save to make sure state after init is same as the start
        #   saves value, fixed, and active state, doesn't load originally free
        #   values, this makes sure original problem spec is same but
        #   initializes the values of free vars
        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")

        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)

        self.deltaP[:].unfix()
        self.ratioP[:].unfix()

        # fix inlet and free outlet
        for t in self.flowsheet().config.time:
            for k, v in self.inlet.vars.items():
                v[t].fix()
            for k, v in self.outlet.vars.items():
                v[t].unfix()
            # to calculate outlet pressure
            Pout = self.outlet.pressure[t]
            Pin = self.inlet.pressure[t]
            if self.deltaP[t].value is not None:
                prdp = value((self.deltaP[t] - Pin) / Pin)
            else:
                prdp = -100  # crazy number to say don't use deltaP as guess
            if value(Pout / Pin) > 1 or value(Pout / Pin) < 0.0:
                if value(self.ratioP[t]) <= 1 and value(self.ratioP[t]) >= 0:
                    Pout.value = value(Pin * self.ratioP[t])
                elif prdp <= 1 and prdp >= 0:
                    Pout.value = value(prdp * Pin)
                else:
                    Pout.value = value(Pin * 0.95)
            self.deltaP[t] = value(Pout - Pin)
            self.ratioP[t] = value(Pout / Pin)

        # Make sure the initialization problem has no degrees of freedom
        # This shouldn't happen here unless there is a bug in this
        dof = degrees_of_freedom(self)
        try:
            assert dof == 0
        except:
            init_log.exception("degrees_of_freedom = {}".format(dof))
            raise

        # one bad thing about reusing this is that the log messages aren't
        # really compatible with being nested inside another initialization
        super().initialize(state_args=state_args,
                           outlvl=outlvl,
                           solver=solver,
                           optarg=optarg)

        # reload original spec
        from_json(self, sd=istate, wts=sp)

    def _get_performance_contents(self, time_point=0):
        pc = super()._get_performance_contents(time_point=time_point)

        pc["vars"]["Opening"] = self.valve_opening[time_point]
        pc["vars"]["Valve Coefficient"] = self.Cv
        if self.config.valve_function == ValveFunctionType.equal_percentage:
            pc["vars"]["alpha"] = self.alpha

        pc["params"] = {}
        pc["params"]["Flow Scaling"] = self.flow_scale

        return pc
コード例 #12
0
class ReactionParameterData(ReactionParameterBlock):
    """
    Property Parameter Block Class

    Contains parameters and indexing sets associated with properties for
    superheated steam.

    """
    # Create Class ConfigBlock
    CONFIG = ConfigBlock()
    CONFIG.declare(
        "gas_property_package",
        ConfigValue(
            description="Reference to associated PropertyPackageParameter "
            "object for the gas phase.",
            domain=is_physical_parameter_block))
    CONFIG.declare(
        "solid_property_package",
        ConfigValue(
            description="Reference to associated PropertyPackageParameter "
            "object for the solid phase.",
            domain=is_physical_parameter_block))
    CONFIG.declare(
        "default_arguments",
        ConfigBlock(
            description="Default arguments to use with Property Package",
            implicit=True))

    def build(self):
        '''
        Callable method for Block construction.
        '''
        super(ReactionParameterBlock, self).build()

        self._reaction_block_class = ReactionBlock

        # Create Phase objects
        self.Vap = VaporPhase()
        self.Sol = SolidPhase()

        # Create Component objects
        self.CH4 = Component()
        self.CO2 = Component()
        self.H2O = Component()
        self.Fe2O3 = Component()
        self.Fe3O4 = Component()
        self.Al2O3 = Component()

        # Component list subsets
        self.gas_component_list = Set(initialize=['CO2', 'H2O', 'CH4'])
        self.sol_component_list = Set(initialize=['Fe2O3', 'Fe3O4', 'Al2O3'])

        # Reaction Index
        self.rate_reaction_idx = Set(initialize=["R1"])

        # Gas Constant
        self.gas_const = Param(within=PositiveReals,
                               mutable=False,
                               default=8.314459848e-3,
                               doc='Gas Constant [kJ/mol.K]')

        # Smoothing factor
        self.eps = Param(mutable=True, default=1e-8, doc='Smoothing Factor')
        # Reaction rate scale factor
        self._scale_factor_rxn = Param(mutable=True,
                                       default=1,
                                       doc='Scale Factor for reaction eqn.'
                                       'Used to help initialization routine')

        # Reaction Stoichiometry
        self.rate_reaction_stoichiometry = {
            ("R1", "Vap", "CH4"): -1,
            ("R1", "Vap", "CO2"): 1,
            ("R1", "Vap", "H2O"): 2,
            ("R1", "Sol", "Fe2O3"): -12,
            ("R1", "Sol", "Fe3O4"): 8,
            ("R1", "Sol", "Al2O3"): 0
        }

        # Reaction stoichiometric coefficient
        self.rxn_stoich_coeff = Param(self.rate_reaction_idx,
                                      default=12,
                                      mutable=True,
                                      doc='Reaction stoichiometric'
                                      'coefficient [-]')

        # Standard Heat of Reaction - kJ/mol_rxn
        dh_rxn_dict = {"R1": 136.5843}
        self.dh_rxn = Param(self.rate_reaction_idx,
                            initialize=dh_rxn_dict,
                            doc="Heat of reaction [kJ/mol]")

        # -------------------------------------------------------------------------
        """ Reaction properties that can be estimated"""

        # Particle grain radius within OC particle
        self.grain_radius = Var(domain=Reals,
                                initialize=2.6e-7,
                                doc='Representative particle grain'
                                'radius within OC particle [m]')
        self.grain_radius.fix()

        # Molar density OC particle
        self.dens_mol_sol = Var(domain=Reals,
                                initialize=32811,
                                doc='Molar density of OC particle [mol/m^3]')
        self.dens_mol_sol.fix()

        # Available volume for reaction - from EPAT report (1-ep)'
        self.a_vol = Var(domain=Reals,
                         initialize=0.28,
                         doc='Available reaction vol. per vol. of OC')
        self.a_vol.fix()

        # Activation Energy
        self.energy_activation = Var(self.rate_reaction_idx,
                                     domain=Reals,
                                     initialize=4.9e1,
                                     doc='Activation energy [kJ/mol]')
        self.energy_activation.fix()

        # Reaction order
        self.rxn_order = Var(self.rate_reaction_idx,
                             domain=Reals,
                             initialize=1.3,
                             doc='Reaction order in gas species [-]')
        self.rxn_order.fix()

        # Pre-exponential factor
        self.k0_rxn = Var(self.rate_reaction_idx,
                          domain=Reals,
                          initialize=8e-4,
                          doc='Pre-exponential factor'
                          '[mol^(1-N_reaction)m^(3*N_reaction -2)/s]')
        self.k0_rxn.fix()

    @classmethod
    def define_metadata(cls, obj):
        obj.add_properties({
            'k_rxn': {
                'method': '_k_rxn',
                'units': 'mol^(1-N_reaction)m^(3*N_reaction -2)/s]'
            },
            'OC_conv': {
                'method': "_OC_conv",
                'units': None
            },
            'OC_conv_temp': {
                'method': "_OC_conv_temp",
                'units': None
            },
            'reaction_rate': {
                'method': "_reaction_rate",
                'units': 'mol_rxn/m3.s'
            }
        })
        obj.add_default_units({
            'time': 's',
            'length': 'm',
            'mass': 'kg',
            'amount': 'mol',
            'temperature': 'K',
            'energy': 'kJ'
        })
コード例 #13
0
class HDAParameterData(PhysicalParameterBlock):
    CONFIG = PhysicalParameterBlock.CONFIG()

    def build(self):
        '''
        Callable method for Block construction.
        '''
        super(HDAParameterData, self).build()

        self._state_block_class = HDAStateBlock

        self.benzene = Component()
        self.toluene = Component()
        self.methane = Component()
        self.hydrogen = Component()
        self.diphenyl = Component()

        self.Vap = VaporPhase()

        # Thermodynamic reference state
        self.pressure_ref = Param(mutable=True,
                                  default=101325,
                                  units=pyunits.Pa,
                                  doc='Reference pressure')
        self.temperature_ref = Param(mutable=True,
                                     default=298.15,
                                     units=pyunits.K,
                                     doc='Reference temperature')

        # Source: The Properties of Gases and Liquids (1987)
        # 4th edition, Chemical Engineering Series - Robert C. Reid
        self.mw_comp = Param(self.component_list,
                             mutable=False,
                             initialize={'benzene': 78.1136E-3,
                                         'toluene': 92.1405E-3,
                                         'hydrogen': 2.016e-3,
                                         'methane': 16.043e-3,
                                         'diphenyl': 154.212e-4},
                             units=pyunits.kg/pyunits.mol,
                             doc="Molecular weight")

        # Constants for specific heat capacity, enthalpy
        # Sources: The Properties of Gases and Liquids (1987)
        #         4th edition, Chemical Engineering Series - Robert C. Reid
        self.cp_mol_ig_comp_coeff_A = Var(
            self.component_list,
            initialize={"benzene": -3.392E1,
                        "toluene": -2.435E1,
                        "hydrogen": 2.714e1,
                        "methane": 1.925e1,
                        "diphenyl": -9.707e1},
            units=pyunits.J/pyunits.mol/pyunits.K,
            doc="Parameter A for ideal gas molar heat capacity")
        self.cp_mol_ig_comp_coeff_A.fix()

        self.cp_mol_ig_comp_coeff_B = Var(
            self.component_list,
            initialize={"benzene": 4.739E-1,
                        "toluene": 5.125E-1,
                        "hydrogen": 9.274e-3,
                        "methane": 5.213e-2,
                        "diphenyl": 1.106e0},
            units=pyunits.J/pyunits.mol/pyunits.K**2,
            doc="Parameter B for ideal gas molar heat capacity")
        self.cp_mol_ig_comp_coeff_B.fix()

        self.cp_mol_ig_comp_coeff_C = Var(
            self.component_list,
            initialize={"benzene": -3.017E-4,
                        "toluene": -2.765E-4,
                        "hydrogen": -1.381e-5,
                        "methane": -8.855e-4,
                        "diphenyl": -8.855e-4},
            units=pyunits.J/pyunits.mol/pyunits.K**3,
            doc="Parameter C for ideal gas molar heat capacity")
        self.cp_mol_ig_comp_coeff_C.fix()

        self.cp_mol_ig_comp_coeff_D = Var(
            self.component_list,
            initialize={"benzene": 7.130E-8,
                        "toluene": 4.911E-8,
                        "hydrogen": 7.645e-9,
                        "methane": -1.132e-8,
                        "diphenyl": 2.790e-7},
            units=pyunits.J/pyunits.mol/pyunits.K**4,
            doc="Parameter D for ideal gas molar heat capacity")
        self.cp_mol_ig_comp_coeff_D.fix()

        # Source: NIST Webook, https://webbook.nist.gov/, retrieved 11/3/2020
        self.enth_mol_form_vap_comp_ref = Var(
            self.component_list,
            initialize={"benzene": -82.9e3,
                        "toluene": -50.1e3,
                        "hydrogen": 0,
                        "methane": -75e3,
                        "diphenyl": -180e3},
            units=pyunits.J/pyunits.mol,
            doc="Standard heat of formation at reference state")
        self.enth_mol_form_vap_comp_ref.fix()

    @classmethod
    def define_metadata(cls, obj):
        """Define properties supported and units."""
        obj.add_properties(
            {'flow_mol': {'method': None},
             'mole_frac_comp': {'method': None},
             'temperature': {'method': None},
             'pressure': {'method': None},
             'mw_comp': {'method': None},
             'dens_mol': {'method': None},
             'enth_mol': {'method': '_enth_mol'}})

        obj.add_default_units({'time': pyunits.s,
                               'length': pyunits.m,
                               'mass': pyunits.kg,
                               'amount': pyunits.mol,
                               'temperature': pyunits.K})
コード例 #14
0
class HDAReactionParameterData(ReactionParameterBlock):
    """
    Property Parameter Block Class
    Contains parameters and indexing sets associated with properties for
    superheated steam.
    """
    def build(self):
        '''
        Callable method for Block construction.
        '''
        super(HDAReactionParameterData, self).build()

        self._reaction_block_class = HDAReactionBlock

        # List of valid phases in property package
        self.phase_list = Set(initialize=['Vap'])

        # Component list - a list of component identifiers
        self.component_list = Set(
            initialize=['benzene', 'toluene', 'hydrogen', 'methane'])

        # Reaction Index
        self.rate_reaction_idx = Set(initialize=["R1"])

        # Reaction Stoichiometry
        self.rate_reaction_stoichiometry = {
            ("R1", "Vap", "benzene"): 1,
            ("R1", "Vap", "toluene"): -1,
            ("R1", "Vap", "hydrogen"): -1,
            ("R1", "Vap", "methane"): 1,
            ("R1", "Liq", "benzene"): 0,
            ("R1", "Liq", "toluene"): 0,
            ("R1", "Liq", "hydrogen"): 0,
            ("R1", "Liq", "methane"): 0
        }

        # Arrhenius Constant
        self.arrhenius = Var(initialize=6.3e+10,
                             doc="Arrhenius pre-exponential factor"
                             )  # TODO: Determine correct value and units
        self.arrhenius.fix()

        # Activation Energy
        self.energy_activation = Var(initialize=217.6e3,
                                     units=pyunits.J / pyunits.mol / pyunits.K,
                                     doc="Activation energy")
        self.energy_activation.fix()

        # Heat of Reaction
        dh_rxn_dict = {"R1": -1.08e5}
        self.dh_rxn = Param(self.rate_reaction_idx,
                            initialize=dh_rxn_dict,
                            doc="Heat of reaction [J/mol]")

        # Gas Constant
        self.gas_const = Param(mutable=False,
                               default=8.314,
                               doc='Gas Constant [J/mol.K]')

    @classmethod
    def define_metadata(cls, obj):
        obj.add_properties({
            'k_rxn': {
                'method': None,
                'units': 'm^3/mol.s'
            },
            'reaction_rate': {
                'method': None,
                'units': 'mol/m^3.s'
            }
        })
        obj.add_default_units({
            'time': pyunits.s,
            'length': pyunits.m,
            'mass': pyunits.kg,
            'amount': pyunits.mol,
            'temperature': pyunits.K
        })
コード例 #15
0
class ElectricalSplitterData(UnitModelBlockData):
    """
    Unit model to split a electricity from a single inlet into multiple outlets based on split fractions
    """
    CONFIG = ConfigBlock()
    CONFIG.declare(
        "dynamic",
        ConfigValue(domain=In([False]),
                    default=False,
                    description="Dynamic model flag - must be False"))
    CONFIG.declare(
        "has_holdup",
        ConfigValue(default=False,
                    domain=In([False]),
                    description="Holdup construction flag - must be False"))
    CONFIG.declare(
        "outlet_list",
        ConfigValue(domain=list_of_strings,
                    description="List of outlet names",
                    doc="""A list containing names of outlets,
                **default** - None.
                **Valid values:** {
                **None** - use num_outlets argument,
                **list** - a list of names to use for outlets.}"""))
    CONFIG.declare(
        "num_outlets",
        ConfigValue(
            domain=int,
            description="Number of outlets to unit",
            doc="""Argument indicating number (int) of outlets to construct,
                not used if outlet_list arg is provided,
                **default** - None.
                **Valid values:** {
                **None** - use outlet_list arg instead, or default to 2 if neither argument
                provided,
                **int** - number of outlets to create (will be named with sequential integers
                from 1 to num_outlets).}"""))

    def build(self):
        """

        """
        super().build()
        time = self.flowsheet().config.time

        self.create_outlets()

        self.electricity = Var(time,
                               domain=NonNegativeReals,
                               initialize=0.0,
                               doc="Electricity into control volume",
                               units=pyunits.kW)
        self.electricity_in = Port(noruleinit=True,
                                   doc="A port for electricity flow")
        self.electricity_in.add(self.electricity, "electricity")

        self.split_fraction = Var(self.outlet_list,
                                  time,
                                  bounds=(0, 1),
                                  initialize=1.0 / len(self.outlet_list),
                                  doc="Split fractions for outlet streams")

        @self.Constraint(time, doc="Split constraint")
        def sum_split(b, t):
            return 1 == sum(b.split_fraction[o, t] for o in b.outlet_list)

        @self.Constraint(time, self.outlet_list, doc="Electricity constraint")
        def electricity_eqn(b, t, o):
            outlet_obj = getattr(b, o + "_elec")
            return outlet_obj[t] == b.split_fraction[o, t] * b.electricity[t]

    def create_outlets(self):
        """
        Create list of outlet stream names based on config arguments.

        Returns:
            list of strings
        """
        config = self.config
        if config.outlet_list is not None and config.num_outlets is not None:
            # If both arguments provided and not consistent, raise Exception
            if len(config.outlet_list) != config.num_outlets:
                raise ConfigurationError(
                    "{} ElectricalSplitter provided with both outlet_list and "
                    "num_outlets arguments, which were not consistent ("
                    "length of outlet_list was not equal to num_outlets). "
                    "Please check your arguments for consistency, and "
                    "note that it is only necessry to provide one of "
                    "these arguments.".format(self.name))
        elif (config.outlet_list is None and config.num_outlets is None):
            # If no arguments provided for outlets, default to num_outlets = 2
            config.num_outlets = 2

        # Create a list of names for outlet StateBlocks
        if config.outlet_list is not None:
            outlet_list = self.config.outlet_list
        else:
            outlet_list = [
                "outlet_{}".format(n) for n in range(1, config.num_outlets + 1)
            ]
        self.outlet_list = outlet_list

        for p in self.outlet_list:
            outlet_obj = Var(self.flowsheet().config.time,
                             domain=NonNegativeReals,
                             initialize=0.0,
                             doc="Electricity at outlet {}".format(p),
                             units=pyunits.kW)
            setattr(self, p + "_elec", outlet_obj)
            outlet_port = Port(noruleinit=True, doc="Outlet {}".format(p))
            outlet_port.add(getattr(self, p + "_elec"), "electricity")
            setattr(self, p + "_port", outlet_port)

    def initialize(self, **kwargs):
        # store original state
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)

        # check for fixed outlet flows and use them to calculate fixed split
        # fractions
        for t in self.flowsheet().config.time:
            for o in self.outlet_list:
                elec_obj = getattr(self, o + "_elec")
                if elec_obj[t].fixed:
                    self.split_fraction[o, t].fix(
                        value(elec_obj[t] / self.electricity[t]))

        # fix or unfix split fractions so n - 1 are fixed
        for t in self.flowsheet().config.time:
            # see how many split fractions are fixed
            n = sum(1 for o in self.outlet_list
                    if self.split_fraction[o, t].fixed)
            # if number of outlets - 1 we're good
            if n == len(self.outlet_list) - 1:
                continue
            # if too many are fixed, unfix the first, generally assume that is
            # the main flow, and is the calculated split fraction
            elif n == len(self.outlet_list):
                self.split_fraction[self.outlet_list[0], t].unfix()
            # if not enough fixed, start fixing from the back until there are
            # are enough
            else:
                for o in reversed(self.outlet_list):
                    if not self.split_fraction[o, t].fixed:
                        self.split_fraction[o, t].fix()
                        n += 1
                    if n == len(self.outlet_list) - 1:
                        break

        self.electricity.fix()
        for o in self.outlet_list:
            getattr(self, o + "_port").unfix()
        assert degrees_of_freedom(self) == 0

        solver = "ipopt"
        if "solver" in kwargs:
            solver = kwargs["solver"]

        opt = SolverFactory(solver)
        opt.solve(self)

        from_json(self, sd=istate, wts=sp)
コード例 #16
0
class TurbineOutletStageData(PressureChangerData):
    # Same settings as the default pressure changer, but force to expander with
    # isentropic efficiency
    CONFIG = PressureChangerData.CONFIG()
    CONFIG.compressor = False
    CONFIG.get("compressor")._default = False
    CONFIG.get("compressor")._domain = In([False])
    CONFIG.thermodynamic_assumption = ThermodynamicAssumption.isentropic
    CONFIG.get("thermodynamic_assumption"
               )._default = ThermodynamicAssumption.isentropic
    CONFIG.get("thermodynamic_assumption")._domain = In(
        [ThermodynamicAssumption.isentropic])

    def build(self):
        super(TurbineOutletStageData, self).build()

        self.flow_coeff = Var(initialize=0.0333,
                              doc="Turbine flow coefficient [kg*C^0.5/s/Pa]")
        self.eff_dry = Var(initialize=0.87,
                           doc="Turbine dry isentropic efficiency")
        self.design_exhaust_flow_vol = Var(
            initialize=6000.0, doc="Design exit volumetirc flowrate [m^3/s]")
        self.efficiency_mech = Var(initialize=0.98,
                                   doc="Turbine mechanical efficiency")
        self.flow_scale = Param(
            mutable=True,
            default=1e-4,
            doc=
            "Scaling factor for pressure flow relation should be approximatly"
            " the same order of magnitude as the expected flow.",
        )
        self.eff_dry.fix()
        self.design_exhaust_flow_vol.fix()
        self.flow_coeff.fix()
        self.efficiency_mech.fix()
        self.ratioP[:] = 1  # make sure these have a number value
        self.deltaP[:] = 0  #   to avoid an error later in initialize

        @self.Expression(self.flowsheet().config.time,
                         doc="Efficiency factor correlation")
        def tel(b, t):
            f = b.control_volume.properties_out[
                t].flow_vol / b.design_exhaust_flow_vol
            return 1e6 * (-0.0035 * f**5 + 0.022 * f**4 - 0.0542 * f**3 +
                          0.0638 * f**2 - 0.0328 * f + 0.0064)

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: Stodola, for choked flow")
        def stodola_equation(b, t):
            flow = b.control_volume.properties_in[t].flow_mol
            mw = b.control_volume.properties_in[t].mw
            Tin = b.control_volume.properties_in[t].temperature
            Pin = b.control_volume.properties_in[t].pressure
            Pr = b.ratioP[t]
            cf = b.flow_coeff
            return (b.flow_scale**2) * flow**2 * mw**2 * (Tin - 273.15) == (
                b.flow_scale**2) * cf**2 * Pin**2 * (1 - Pr**2)

        @self.Expression(
            self.flowsheet().config.time,
            doc="Equation: isentropic specific enthalpy change",
        )
        def delta_enth_isentropic(b, t):
            flow = b.control_volume.properties_in[t].flow_mol
            work_isen = b.work_isentropic[t]
            return work_isen / flow

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: Efficiency correlation")
        def efficiency_correlation(b, t):
            x = b.control_volume.properties_out[t].vapor_frac
            eff = b.efficiency_isentropic[t]
            dh_isen = b.delta_enth_isentropic[t]
            tel = b.tel[t]
            return eff == b.eff_dry * x * (1 - 0.65 *
                                           (1 - x)) * (1 + tel / dh_isen)

        @self.Expression(self.flowsheet().config.time,
                         doc="Thermodynamic power [J/s]")
        def power_thermo(b, t):
            return b.control_volume.work[t]

        @self.Expression(self.flowsheet().config.time, doc="Shaft power [J/s]")
        def power_shaft(b, t):
            return b.power_thermo[t] * b.efficiency_mech

    def _get_performance_contents(self, time_point=0):
        pc = super()._get_performance_contents(time_point=time_point)
        pc["vars"]["Mechanical Efficiency"] = self.efficiency_mech
        pc["vars"]["Flow Coefficient"] = self.flow_coeff
        pc["vars"]["Isentropic Efficieincy (Dry)"] = self.eff_dry
        pc["vars"]["Design Exhaust Flow"] = self.design_exhaust_flow_vol

        pc["exprs"] = {}
        pc["exprs"]["Thermodynamic Power"] = self.power_thermo[time_point]
        pc["exprs"]["Shaft Power"] = self.power_shaft[time_point]

        pc["params"] = {}
        pc["params"]["Flow Scaling"] = self.flow_scale

        return pc

    def initialize(
        self,
        state_args={},
        outlvl=idaeslog.NOTSET,
        solver="ipopt",
        optarg={
            "tol": 1e-6,
            "max_iter": 30
        },
    ):
        """
        Initialize the outlet turbine stage model.  This deactivates the
        specialized constraints, then does the isentropic turbine initialization,
        then reactivates the constraints and solves.

        Args:
            state_args (dict): Initial state for property initialization
            outlvl : sets output level of initialization routine
            solver (str): Solver to use for initialization
            optarg (dict): Solver arguments dictionary
        """
        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")

        # sp is what to save to make sure state after init is same as the start
        #   saves value, fixed, and active state, doesn't load originally free
        #   values, this makes sure original problem spec is same but initializes
        #   the values of free vars
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)
        # Deactivate special constraints
        self.stodola_equation.deactivate()
        self.efficiency_correlation.deactivate()
        self.deltaP.unfix()
        self.ratioP.unfix()
        # Fix turbine parameters + eff_isen
        self.eff_dry.fix()
        self.design_exhaust_flow_vol.fix()
        self.flow_coeff.fix()

        # fix inlet and free outlet
        for t in self.flowsheet().config.time:
            for k, v in self.inlet.vars.items():
                v[t].fix()
            for k, v in self.outlet.vars.items():
                v[t].unfix()
            # If there isn't a good guess for efficiency or outlet pressure
            # provide something reasonable.
            eff = self.efficiency_isentropic[t]
            eff.fix(
                eff.value if value(eff) > 0.3 and value(eff) < 1.0 else 0.8)
            # for outlet pressure try outlet pressure, pressure ratio, delta P,
            # then if none of those look reasonable use a pressure ratio of 0.8
            # to calculate outlet pressure
            Pout = self.outlet.pressure[t]
            Pin = self.inlet.pressure[t]
            prdp = value((self.deltaP[t] - Pin) / Pin)
            if value(Pout / Pin) > 0.95 or value(Pout / Pin) < 0.003:
                if value(self.ratioP[t]) < 0.9 and value(
                        self.ratioP[t]) > 0.01:
                    Pout.fix(value(Pin * self.ratioP))
                elif prdp < 0.9 and prdp > 0.01:
                    Pout.fix(value(prdp * Pin))
                else:
                    Pout.fix(value(Pin * 0.3))
            else:
                Pout.fix()
        self.deltaP[:] = value(Pout - Pin)
        self.ratioP[:] = value(Pout / Pin)

        for t in self.flowsheet().config.time:
            self.properties_isentropic[t].pressure.value = value(
                self.outlet.pressure[t])
            self.properties_isentropic[t].flow_mol.value = value(
                self.inlet.flow_mol[t])
            self.properties_isentropic[t].enth_mol.value = value(
                self.inlet.enth_mol[t] * 0.95)
            self.outlet.flow_mol[t].value = value(self.inlet.flow_mol[t])
            self.outlet.enth_mol[t].value = value(self.inlet.enth_mol[t] *
                                                  0.95)

        # Make sure the initialization problem has no degrees of freedom
        # This shouldn't happen here unless there is a bug in this
        dof = degrees_of_freedom(self)
        try:
            assert dof == 0
        except AssertionError:
            init_log.error("Degrees of freedom not 0, ({})".format(dof))
            raise

        mw = self.control_volume.properties_in[0].mw
        Tin = self.control_volume.properties_in[0].temperature
        Pin = self.control_volume.properties_in[0].pressure
        Pr = self.ratioP[0]
        cf = self.flow_coeff
        self.inlet.flow_mol.fix(
            value(cf * Pin * sqrt(1 - Pr**2) / mw / sqrt(Tin - 273.15)))

        # one bad thing about reusing this is that the log messages aren't
        # really compatible with being nested inside another initialization
        super().initialize(state_args=state_args,
                           outlvl=outlvl,
                           solver=solver,
                           optarg=optarg)
        # Free eff_isen and activate sepcial constarints
        self.efficiency_isentropic.unfix()
        self.outlet.pressure.fix()
        self.inlet.flow_mol.unfix()
        self.stodola_equation.activate()
        self.efficiency_correlation.activate()

        slvr = SolverFactory(solver)
        slvr.options = optarg
        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = slvr.solve(self, tee=slc.tee)
        init_log.info("Initialization Complete (Outlet Stage): {}".format(
            idaeslog.condition(res)))

        # reload original spec
        from_json(self, sd=istate, wts=sp)
コード例 #17
0
class HydrogenTankData(UnitModelBlockData):
    """
    Simple hydrogen tank model.
    Unit model to store or supply compressed hydrogen.
    
    """
    CONFIG = ConfigBlock()

    # This model is based on steady state material & energy balances.
    # The accumulation term is computed based on the tank state at
    # previous time step. Thus, dynamic option is turned off.
    # However, a dynamic analysis can be performed by creating
    # an instance of this model for every time step.
    CONFIG.declare(
        "dynamic",
        ConfigValue(domain=In([False]),
                    default=False,
                    description="Dynamic model flag - must be False",
                    doc="""Indicats if Hydrogen tank model is dynamic,
**default** = False. Equilibrium Reactors do not support dynamic behavior."""))
    CONFIG.declare(
        "has_holdup",
        ConfigValue(
            default=False,
            domain=In([False]),
            description="Holdup construction flag - must be False",
            doc="""Indicates whether holdup terms should be constructed or not.
**default** - False. Hydrogen tank model uses custom equations for holdup."""))
    CONFIG.declare(
        "momentum_balance_type",
        ConfigValue(
            default=MomentumBalanceType.pressureTotal,
            domain=In(MomentumBalanceType),
            description="Momentum balance construction flag",
            doc="""Indicates what type of momentum balance should be constructed,
**default** - MomentumBalanceType.pressureTotal.
**Valid values:** {
**MomentumBalanceType.none** - exclude momentum balances,
**MomentumBalanceType.pressureTotal** - single pressure balance for material,
**MomentumBalanceType.pressurePhase** - pressure balances for each phase,
**MomentumBalanceType.momentumTotal** - single momentum balance for material,
**MomentumBalanceType.momentumPhase** - momentum balances for each phase.}"""))
    CONFIG.declare(
        "property_package",
        ConfigValue(
            default=useDefault,
            domain=is_physical_parameter_block,
            description="Property package to use for control volume",
            doc=
            """Property parameter object used to define property calculations,
**default** - useDefault.
**Valid values:** {
**useDefault** - use default package from parent model or flowsheet,
**PhysicalParameterObject** - a PhysicalParameterBlock object.}"""))
    CONFIG.declare(
        "property_package_args",
        ConfigBlock(
            implicit=True,
            description="Arguments to use for constructing property packages",
            doc=
            """A ConfigBlock with arguments to be passed to a property block(s)
and used when constructing these,
**default** - None.
**Valid values:** {
see property package for documentation.}"""))

    def build(self):
        """Building model
        Args:
            None
        Returns:
            None
        """
        super().build()

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

        # add inlet and outlet states
        self.control_volume.add_state_blocks(has_phase_equilibrium=False)

        # add tank volume
        self.control_volume.add_geometry()

        # add phase fractions
        self.control_volume._add_phase_fractions()

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

        # add a state block 'previous_state' for the storage tank
        # this state block is needed to compute material and energy holdup
        # at previous or the starting time step using the property package
        # for a given P_prev and T_prev
        # NOTE: there is no flow in the previous state so,
        # flow_mol state variable is fixed to 0
        self.previous_state = (self.config.property_package.build_state_block(
            self.flowsheet().config.time, doc="Tank state at previous time"))

        # previous state should not have any flow
        self.previous_state[:].flow_mol.fix(0)

        # add local lists for easy use
        phase_list = self.control_volume.properties_in.phase_list
        pc_set = self.control_volume.properties_in.phase_component_set
        component_list = self.control_volume.properties_in.component_list

        # Get units from property package
        units = self.config.property_package.\
            get_metadata().get_derived_units
        if (self.control_volume.properties_in[
                self.flowsheet().config.time.first()].get_material_flow_basis(
                ) == MaterialFlowBasis.molar):
            flow_units = units("flow_mole")
            material_units = units("amount")
        elif (self.control_volume.properties_in[
                self.flowsheet().config.time.first()].get_material_flow_basis(
                ) == MaterialFlowBasis.mass):
            flow_units = units("flow_mass")
            material_units = units("mass")

        # Add Inlet and Outlet Ports
        self.add_inlet_port()
        self.add_outlet_port()

        # Define Vars for Tank volume calculations
        self.tank_diameter = Var(self.flowsheet().config.time,
                                 within=NonNegativeReals,
                                 initialize=1.0,
                                 doc="Diameter of storage tank in m",
                                 units=units("length"))

        self.tank_length = Var(self.flowsheet().config.time,
                               within=NonNegativeReals,
                               initialize=1.0,
                               doc="Length of storage tank in m",
                               units=units("length"))

        # Tank volume calculation
        @self.Constraint(self.flowsheet().config.time)
        def volume_cons(b, t):
            return (b.control_volume.volume[t] == const.pi * b.tank_length[t] *
                    ((b.tank_diameter[t] / 2)**2))

        # define Vars for the model
        self.dt = Var(self.flowsheet().config.time,
                      domain=NonNegativeReals,
                      initialize=100,
                      doc="Time step for holdup calculation",
                      units=units("time"))

        self.heat_duty = Var(
            self.flowsheet().config.time,
            domain=Reals,
            initialize=0.0,
            doc="Heat transferred from surroundings, 0 for adiabatic",
            units=units("power"))

        self.material_accumulation = Var(
            self.flowsheet().config.time,
            pc_set,
            within=Reals,
            initialize=1.0,
            doc="Accumulation of material in tank",
            units=flow_units)

        self.energy_accumulation = Var(self.flowsheet().config.time,
                                       phase_list,
                                       within=Reals,
                                       initialize=1.0,
                                       doc="Energy accumulation",
                                       units=units("power"))

        self.material_holdup = Var(self.flowsheet().config.time,
                                   pc_set,
                                   within=Reals,
                                   initialize=1.0,
                                   doc="Material holdup in tank",
                                   units=material_units)

        self.energy_holdup = Var(self.flowsheet().config.time,
                                 phase_list,
                                 within=Reals,
                                 initialize=1.0,
                                 doc="Energy holdup in tank",
                                 units=units("energy"))

        self.previous_material_holdup = Var(
            self.flowsheet().config.time,
            pc_set,
            within=Reals,
            initialize=1.0,
            doc="Tank material holdup at previous time",
            units=material_units)

        self.previous_energy_holdup = Var(
            self.flowsheet().config.time,
            phase_list,
            within=Reals,
            initialize=1.0,
            doc="Tank energy holdup at previous time",
            units=units("energy"))

        # Adiabatic operations are assumed
        # Fixing the heat_duty to 0 here to avoid any misakes at use
        # TODO: remove this once the isothermal constraints are added
        self.heat_duty.fix(0)

        # Computing material and energy holdup in the tank at previous time
        # using previous state Pressure and Temperature of the tank
        @self.Constraint(self.flowsheet().config.time,
                         pc_set,
                         doc="Material holdup at previous time")
        def previous_material_holdup_rule(b, t, p, j):
            return (
                b.previous_material_holdup[t, p,
                                           j] == b.control_volume.volume[t] *
                b.control_volume.phase_fraction[t, p] *
                b.previous_state[t].get_material_density_terms(p, j))

        @self.Constraint(self.flowsheet().config.time,
                         phase_list,
                         doc="Energy holdup at previous time")
        def previous_energy_holdup_rule(b, t, p):
            if (self.control_volume.properties_in[t].get_material_flow_basis()
                    == MaterialFlowBasis.molar):
                return (b.previous_energy_holdup[t, p] == (
                    sum(b.previous_material_holdup[t, p, j]
                        for j in component_list) *
                    b.previous_state[t].energy_internal_mol_phase[p]))
            if (self.control_volume.properties_in[t].get_material_flow_basis()
                    == MaterialFlowBasis.mass):
                return (b.previous_energy_holdup[t, p] == (
                    sum(b.previous_material_holdup[t, p, j]
                        for j in component_list) *
                    (b.previous_state[t].energy_internal_mol_phase[p] /
                     b.previous_state[t].mw)))

        # component material balances
        @self.Constraint(self.flowsheet().config.time,
                         pc_set,
                         doc="Material balances")
        def material_balances(b, t, p, j):
            if (p, j) in pc_set:
                return (
                    b.material_accumulation[t, p, j] ==
                    (b.control_volume.properties_in[t].\
                     get_material_flow_terms(p, j) -
                     b.control_volume.properties_out[t].\
                         get_material_flow_terms(p, j))
                    )
            else:
                return Constraint.Skip

        # integration of material accumulation
        @self.Constraint(self.flowsheet().config.time,
                         pc_set,
                         doc="Material holdup integration")
        def material_holdup_integration(b, t, p, j):
            if (p, j) in pc_set:
                return b.material_holdup[t, p, j] == (
                    b.dt[t] * b.material_accumulation[t, p, j] +
                    b.previous_material_holdup[t, p, j])

        # material holdup calculation
        @self.Constraint(self.flowsheet().config.time,
                         pc_set,
                         doc="Material holdup calculations")
        def material_holdup_calculation(b, t, p, j):
            if (p, j) in pc_set:
                return (
                    b.material_holdup[t, p, j] == (
                        b.control_volume.volume[t] *
                        b.control_volume.phase_fraction[t, p] *
                        b.control_volume.properties_out[t].\
                            get_material_density_terms(p, j)))

        # energy accumulation
        @self.Constraint(self.flowsheet().config.time,
                         doc="Energy accumulation")
        def energy_accumulation_equation(b, t):
            return (sum(b.energy_accumulation[t, p] for p in phase_list) *
                    b.dt[t] == sum(b.energy_holdup[t, p] for p in phase_list) -
                    sum(b.previous_energy_holdup[t, p] for p in phase_list))

        # energy holdup calculation
        @self.Constraint(self.flowsheet().config.time,
                         phase_list,
                         doc="Energy holdup calculation")
        def energy_holdup_calculation(b, t, p):
            if (self.control_volume.properties_in[t].get_material_flow_basis()
                    == MaterialFlowBasis.molar):
                return (
                    b.energy_holdup[t, p] ==
                    (sum(b.material_holdup[t, p, j] for j in component_list)
                    * b.control_volume.properties_out[t].\
                    energy_internal_mol_phase[p])
                    )
            if (self.control_volume.properties_in[t].get_material_flow_basis()
                    == MaterialFlowBasis.mass):
                return (
                    b.energy_holdup[t, p] ==
                    (sum(b.material_holdup[t, p, j] for j in component_list)
                    * (b.control_volume.properties_out[t].\
                    energy_internal_mol_phase[p]/
                    b.control_volume.properties_out[t].mw))
                    )

        # Energy balance based on internal energy, as follows:
        #     n_final * U_final =
        #                        n_previous * U_previous +
        #                        n_inlet * H_inlet - n_outlet * H_outlet
        #     where, n is number of moles, U is internal energy, H is enthalpy
        @self.Constraint(self.flowsheet().config.time, doc="Energy balance")
        def energy_balances(b, t):
            return (sum(
                b.energy_holdup[t, p]
                for p in phase_list) == sum(b.previous_energy_holdup[t, p]
                                            for p in phase_list) + b.dt[t] *
                    (sum(b.control_volume.properties_in[t].
                         get_enthalpy_flow_terms(p) for p in phase_list) -
                     sum(b.control_volume.properties_out[t].
                         get_enthalpy_flow_terms(p) for p in phase_list)))

    def initialize(blk,
                   state_args=None,
                   outlvl=idaeslog.NOTSET,
                   solver=None,
                   optarg=None):
        '''
        Hydrogen tank model 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

                     * 0 = no output (default)
                     * 1 = return solver state for each step in routine
                     * 2 = return solver state for each step in subroutines
                     * 3 = include solver output infomation (tee=True)

            optarg : solver options dictionary object (default={'tol': 1e-6})
            solver : str indicating whcih solver to use during
                     initialization (default = 'ipopt')

        Returns:
            None
        '''

        if state_args is None:
            state_args = dict()

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

        opt = get_solver(solver, optarg)

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

        flags = blk.control_volume.initialize(state_args=state_args,
                                              outlvl=outlvl,
                                              optarg=optarg,
                                              solver=solver)
        flag_previous_state = blk.previous_state.initialize(
            outlvl=outlvl,
            optarg=optarg,
            solver=solver,
            hold_state=True,
            state_args=state_args,
        )
        blk.previous_state[0].sum_mole_frac_out.deactivate()

        init_log.info_high("Initialization Step 1 Complete.")

        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)))
        blk.previous_state[0].sum_mole_frac_out.activate()
        blk.control_volume.release_state(flags, outlvl)
        blk.previous_state.release_state(flag_previous_state, outlvl)
        init_log.info("Initialization Complete.")

    def calculate_scaling_factors(self):
        super().calculate_scaling_factors()

        if hasattr(self, "previous_state"):
            for t, v in self.previous_state.items():
                iscale.set_scaling_factor(v.flow_mol, 1e-3)
                iscale.set_scaling_factor(v.pressure, 1e-5)
                iscale.set_scaling_factor(v.temperature, 1e-1)

        if hasattr(self, "tank_diameter"):
            for t, v in self.tank_diameter.items():
                iscale.set_scaling_factor(v, 1)

        if hasattr(self, "tank_length"):
            for t, v in self.tank_length.items():
                iscale.set_scaling_factor(v, 1)

        if hasattr(self, "heat_duty"):
            for t, v in self.heat_duty.items():
                iscale.set_scaling_factor(v, 1e-5)

        if hasattr(self, "material_accumulation"):
            for (t, p, j), v in self.material_accumulation.items():
                iscale.set_scaling_factor(v, 1e-3)

        if hasattr(self, "energy_accumulation"):
            for (t, p), v in self.energy_accumulation.items():
                iscale.set_scaling_factor(v, 1e-3)

        if hasattr(self, "material_holdup"):
            for (t, p, j), v in self.material_holdup.items():
                iscale.set_scaling_factor(v, 1e-5)

        if hasattr(self, "energy_holdup"):
            for (t, p), v in self.energy_holdup.items():
                iscale.set_scaling_factor(v, 1e-5)

        if hasattr(self, "previous_material_holdup"):
            for (t, p, j), v in self.previous_material_holdup.items():
                iscale.set_scaling_factor(v, 1e-5)

        if hasattr(self, "previous_energy_holdup"):
            for (t, p), v in self.previous_energy_holdup.items():
                iscale.set_scaling_factor(v, 1e-5)

        # Volume constraint
        if hasattr(self, "volume_cons"):
            for t, c in self.volume_cons.items():
                iscale.constraint_scaling_transform(
                    c,
                    iscale.get_scaling_factor(self.tank_length[t],
                                              default=1,
                                              warning=True))

        # Previous time Material Holdup Rule
        if hasattr(self, "previous_material_holdup_rule"):
            for (t, i), c in self.previous_material_holdup_rule.items():
                iscale.constraint_scaling_transform(
                    c,
                    iscale.get_scaling_factor(self.material_holdup[t, i, j],
                                              default=1,
                                              warning=True))

        # Previous time Energy Holdup Rule
        if hasattr(self, "previous_energy_holdup_rule"):
            for (t, i), c in self.previous_energy_holdup_rule.items():
                iscale.constraint_scaling_transform(
                    c,
                    iscale.get_scaling_factor(self.energy_holdup[t, i],
                                              default=1,
                                              warning=True))

        # Material Balances
        if hasattr(self, "material_balances"):
            for (t, i, j), c in self.material_balances.items():
                iscale.constraint_scaling_transform(
                    c,
                    iscale.get_scaling_factor(self.material_accumulation[t, i,
                                                                         j],
                                              default=1,
                                              warning=True))

        # Material Holdup Integration
        if hasattr(self, "material_holdup_integration"):
            for (t, i, j), c in self.material_holdup_integration.items():
                iscale.constraint_scaling_transform(
                    c,
                    iscale.get_scaling_factor(self.material_holdup[t, i, j],
                                              default=1,
                                              warning=True))

        # Material Holdup Constraints
        if hasattr(self, "material_holdup_calculation"):
            for (t, i, j), c in self.material_holdup_calculation.items():
                iscale.constraint_scaling_transform(
                    c,
                    iscale.get_scaling_factor(self.material_holdup[t, i, j],
                                              default=1,
                                              warning=True))

        # Enthalpy Balances
        if hasattr(self, "energy_accumulation_equation"):
            for t, c in self.energy_accumulation_equation.items():
                iscale.constraint_scaling_transform(
                    c,
                    iscale.get_scaling_factor(self.energy_accumulation[t, p],
                                              default=1,
                                              warning=True))

        # Energy Holdup Integration
        if hasattr(self, "energy_holdup_calculation"):
            for (t, i), c in self.energy_holdup_calculation.items():
                iscale.constraint_scaling_transform(
                    c,
                    iscale.get_scaling_factor(self.energy_holdup[t, i],
                                              default=1,
                                              warning=True))

        # Energy Balance Equation
        if hasattr(self, "energy_balances"):
            for t, c in self.energy_balances.items():
                iscale.constraint_scaling_transform(
                    c,
                    iscale.get_scaling_factor(self.energy_holdup[t, i],
                                              default=1,
                                              warning=True))
コード例 #18
0
class TurbineStageData(PressureChangerData):
    # Same settings as the default pressure changer, but force to expander with
    # isentropic efficiency
    CONFIG = PressureChangerData.CONFIG()
    CONFIG.compressor = False
    CONFIG.get("compressor")._default = False
    CONFIG.get("compressor")._domain = In([False])
    CONFIG.thermodynamic_assumption = ThermodynamicAssumption.isentropic
    CONFIG.get("thermodynamic_assumption"
               )._default = ThermodynamicAssumption.isentropic
    CONFIG.get("thermodynamic_assumption")._domain = In(
        [ThermodynamicAssumption.isentropic])

    def build(self):
        super(TurbineStageData, self).build()

        self.efficiency_mech = Var(initialize=0.98,
                                   doc="Turbine mechanical efficiency")
        self.efficiency_mech.fix()
        self.ratioP[:] = 0.8  # make sure these have a number value
        self.deltaP[:] = 0  #   to avoid an error later in initialize
        time_set = self.flowsheet().config.time

        self.shaft_speed = Var(time_set,
                               doc="Shaft speed [1/s]",
                               initialize=60.0)

        @self.Expression(time_set, doc="Specific speed [dimensionless]")
        def specific_speed(b, t):
            s = b.shaft_speed[t]  # 1/s
            v = b.control_volume.properties_out[t].flow_vol  # m3/s
            his_rate = b.work_isentropic[t]  # J/s
            m = b.control_volume.properties_out[t].flow_mass  # kg/s
            return s * v**0.5 * (his_rate / m)**(-0.75)  # dimensionless

        @self.Expression(time_set, doc="Thermodynamic power [J/s]")
        def power_thermo(b, t):
            return b.control_volume.work[t]

        @self.Expression(self.flowsheet().config.time, doc="Shaft power [J/s]")
        def power_shaft(b, t):
            return b.power_thermo[t] * b.efficiency_mech

    def _get_performance_contents(self, time_point=0):
        pc = super()._get_performance_contents(time_point=time_point)
        pc["vars"]["Mechanical Efficiency"] = self.efficiency_mech

        return pc

    def initialize(
        self,
        state_args={},
        outlvl=0,
        solver="ipopt",
        optarg={
            "tol": 1e-6,
            "max_iter": 30
        },
    ):
        """
        Initialize the turbine stage model.  This deactivates the
        specialized constraints, then does the isentropic turbine initialization,
        then reactivates the constraints and solves.

        Args:
            state_args (dict): Initial state for property initialization
            outlvl (int): Amount of output (0 to 3) 0 is lowest
            solver (str): Solver to use for initialization
            optarg (dict): Solver arguments dictionary
        """
        stee = True if outlvl >= 3 else False
        # sp is what to save to make sure state after init is same as the start
        #   saves value, fixed, and active state, doesn't load originally free
        #   values, this makes sure original problem spec is same but initializes
        #   the values of free vars
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)

        # fix inlet and free outlet
        for t in self.flowsheet().config.time:
            for k, v in self.inlet.vars.items():
                v[t].fix()
            for k, v in self.outlet.vars.items():
                v[t].unfix()
            # If there isn't a good guess for efficiency or outlet pressure
            # provide something reasonable.
            eff = self.efficiency_isentropic[t]
            eff.fix(
                eff.value if value(eff) > 0.3 and value(eff) < 1.0 else 0.8)
            # for outlet pressure try outlet pressure, pressure ratio, delta P,
            # then if none of those look reasonable use a pressure ratio of 0.8
            # to calculate outlet pressure
            Pout = self.outlet.pressure[t]
            Pin = self.inlet.pressure[t]
            prdp = value((self.deltaP[t] - Pin) / Pin)
            if self.deltaP[t].fixed:
                Pout.value = value(Pin - Pout)
            if self.ratioP[t].fixed:
                Pout.value = value(self.ratioP[t] * Pin)
            if value(Pout / Pin) > 0.99 or value(Pout / Pin) < 0.1:
                if value(self.ratioP[t]) < 0.99 and value(
                        self.ratioP[t]) > 0.1:
                    Pout.fix(value(Pin * self.ratioP[t]))
                elif prdp < 0.99 and prdp > 0.1:
                    Pout.fix(value(prdp * Pin))
                else:
                    Pout.fix(value(Pin * 0.8))
            else:
                Pout.fix()
            self.deltaP[t] = value(Pout - Pin)
            self.ratioP[t] = value(Pout / Pin)

        self.deltaP[:].unfix()
        self.ratioP[:].unfix()

        for t in self.flowsheet().config.time:
            self.properties_isentropic[t].pressure.value = value(
                self.outlet.pressure[t])
            self.properties_isentropic[t].flow_mol.value = value(
                self.inlet.flow_mol[t])
            self.properties_isentropic[t].enth_mol.value = value(
                self.inlet.enth_mol[t] * 0.95)
            self.outlet.flow_mol[t].value = value(self.inlet.flow_mol[t])
            self.outlet.enth_mol[t].value = value(self.inlet.enth_mol[t] *
                                                  0.95)

        # Make sure the initialization problem has no degrees of freedom
        # This shouldn't happen here unless there is a bug in this
        dof = degrees_of_freedom(self)
        try:
            assert dof == 0
        except:
            _log.exception("degrees_of_freedom = {}".format(dof))
            raise

        # one bad thing about reusing this is that the log messages aren't
        # really compatible with being nested inside another initialization
        super(TurbineStageData, self).initialize(state_args=state_args,
                                                 outlvl=outlvl,
                                                 solver=solver,
                                                 optarg=optarg)

        # reload original spec
        from_json(self, sd=istate, wts=sp)
コード例 #19
0
ファイル: turbine_inlet.py プロジェクト: spevenhe/idaes-pse
class TurbineInletStageData(PressureChangerData):
    # Same settings as the default pressure changer, but force to expander with
    # isentropic efficiency
    CONFIG = PressureChangerData.CONFIG()
    CONFIG.compressor = False
    CONFIG.get('compressor')._default = False
    CONFIG.get('compressor')._domain = In([False])
    CONFIG.thermodynamic_assumption = ThermodynamicAssumption.isentropic
    CONFIG.get('thermodynamic_assumption')._default = \
        ThermodynamicAssumption.isentropic
    CONFIG.get('thermodynamic_assumption')._domain = \
        In([ThermodynamicAssumption.isentropic])
    def build(self):
        super(TurbineInletStageData, self).build()

        self.flow_coeff = Var(self.flowsheet().config.time,
                              initialize=1.053/3600.0,
            doc="Turbine flow coefficient [kg*C^0.5/Pa/s]")
        self.delta_enth_isentropic = Var(self.flowsheet().config.time,
                                         initialize=-1000,
            doc="Specific enthalpy change of isentropic process [J/mol]")
        self.blade_reaction = Var(initialize=0.9,
            doc="Blade reaction parameter")
        self.blade_velocity = Var(initialize=110.0,
            doc="Design blade velocity [m/s]")
        self.eff_nozzle = Var(initialize=0.95, bounds=(0.0, 1.0),
            doc="Nozzel efficiency (typically 0.90 to 0.95)")
        self.efficiency_mech = Var(initialize=0.98,
            doc="Turbine mechanical efficiency")
        self.flow_scale = Param(mutable=True, default=1e3, doc=
            "Scaling factor for pressure flow relation should be approximatly"
            " the same order of magnitude as the expected flow.")
        self.eff_nozzle.fix()
        self.blade_reaction.fix()
        self.flow_coeff.fix()
        self.blade_velocity.fix()
        self.efficiency_mech.fix()
        self.ratioP[:] = 1 # make sure these have a number value
        self.deltaP[:] = 0 #   to avoid an error later in initialize

        @self.Expression(self.flowsheet().config.time,
            doc="Entering steam velocity calculation [m/s]")
        def steam_entering_velocity(b, t):
            # 1.414 = 44.72/sqrt(1000) for SI if comparing to Liese (2014)
            # b.delta_enth_isentropic[t] = -(hin - hiesn), the mw converts
            # enthalpy to a mass basis
            return 1.414*sqrt(-(1-b.blade_reaction)*b.delta_enth_isentropic[t]/
                    b.control_volume.properties_in[t].mw*self.eff_nozzle)

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: Turbine inlet flow")
        def inlet_flow_constraint(b, t):
            # Some local vars to make the equation more readable
            g = b.control_volume.properties_in[t].heat_capacity_ratio
            mw = b.control_volume.properties_in[t].mw
            flow = b.control_volume.properties_in[t].flow_mol
            Tin = b.control_volume.properties_in[t].temperature
            cf = b.flow_coeff[t]
            Pin = b.control_volume.properties_in[t].pressure
            Pratio = b.ratioP[t]
            return ((1/b.flow_scale**2)*flow**2*mw**2*(Tin - 273.15) ==
                (1/b.flow_scale**2)*cf**2*Pin**2*
                (g/(g - 1)*(Pratio**(2.0/g) - Pratio**((g + 1)/g))))

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: Isentropic enthalpy change")
        def isentropic_enthalpy(b, t):
            return b.work_isentropic[t] == (b.delta_enth_isentropic[t]*
                b.control_volume.properties_in[t].flow_mol)

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: Efficiency")
        def efficiency_correlation(b, t):
            Vr = b.blade_velocity/b.steam_entering_velocity[t]
            eff = b.efficiency_isentropic[t]
            R = b.blade_reaction
            return eff == 2*Vr*((sqrt(1 - R) - Vr) +
                                 sqrt((sqrt(1 - R) - Vr)**2 + R))
        @self.Expression(self.flowsheet().config.time,
                         doc="Thermodynamic power [J/s]")
        def power_thermo(b, t):
            return b.control_volume.work[t]

        @self.Expression(self.flowsheet().config.time, doc="Shaft power [J/s]")
        def power_shaft(b, t):
            return b.power_thermo[t]*b.efficiency_mech

    def initialize(self, state_args={}, outlvl=0, solver='ipopt',
        optarg={'tol': 1e-6, 'max_iter':30}):
        """
        Initialize the inlet turbine stage model.  This deactivates the
        specialized constraints, then does the isentropic turbine initialization,
        then reactivates the constraints and solves.

        Args:
            state_args (dict): Initial state for property initialization
            outlvl (int): Amount of output (0 to 3) 0 is lowest
            solver (str): Solver to use for initialization
            optarg (dict): Solver arguments dictionary
        """
        stee = True if outlvl >= 3 else False
        # sp is what to save to make sure state after init is same as the start
        #   saves value, fixed, and active state, doesn't load originally free
        #   values, this makes sure original problem spec is same but initializes
        #   the values of free vars
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)
        # Deactivate special constraints
        self.inlet_flow_constraint.deactivate()
        self.isentropic_enthalpy.deactivate()
        self.efficiency_correlation.deactivate()
        self.deltaP.unfix()
        self.ratioP.unfix()

        # Fix turbine parameters + eff_isen
        self.eff_nozzle.fix()
        self.blade_reaction.fix()
        self.flow_coeff.fix()
        self.blade_velocity.fix()

        # fix inlet and free outlet
        for t in self.flowsheet().config.time:
            for k, v in self.inlet.vars.items():
                v[t].fix()
            for k, v in self.outlet.vars.items():
                v[t].unfix()
            # If there isn't a good guess for efficeny or outlet pressure
            # provide something reasonable.
            eff = self.efficiency_isentropic[t]
            eff.fix(eff.value if value(eff) > 0.3 and value(eff) < 1.0 else 0.8)
            # for outlet pressure try outlet pressure, pressure ratio, delta P,
            # then if none of those look reasonable use a pressure ratio of 0.8
            # to calculate outlet pressure
            Pout = self.outlet.pressure[t]
            Pin = self.inlet.pressure[t]
            prdp = value((self.deltaP[t] - Pin)/Pin)
            if value(Pout/Pin) > 0.98 or value(Pout/Pin) < 0.3:
                if value(self.ratioP[t]) < 0.98 and value(self.ratioP[t]) > 0.3:
                    Pout.fix(value(Pin*self.ratioP))
                elif prdp < 0.98 and prdp > 0.3:
                    Pout.fix(value(prdp*Pin))
                else:
                    Pout.fix(value(Pin*0.8))
            else:
                Pout.fix()
        self.deltaP[:] = value(Pout - Pin)
        self.ratioP[:] = value(Pout/Pin)

        for t in self.flowsheet().config.time:
            self.properties_isentropic[t].pressure.value = \
                value(self.outlet.pressure[t])
            self.properties_isentropic[t].flow_mol.value = \
                value(self.inlet.flow_mol[t])
            self.properties_isentropic[t].enth_mol.value = \
                value(self.inlet.enth_mol[t]*0.95)
            self.outlet.flow_mol[t].value = \
                value(self.inlet.flow_mol[t])
            self.outlet.enth_mol[t].value = \
                value(self.inlet.enth_mol[t]*0.95)

        # Make sure the initialization problem has no degrees of freedom
        # This shouldn't happen here unless there is a bug in this
        dof = degrees_of_freedom(self)
        try:
            assert(dof == 0)
        except:
            _log.exception("degrees_of_freedom = {}".format(dof))
            raise

        # one bad thing about reusing this is that the log messages aren't
        # really compatible with being nested inside another initialization
        super(TurbineInletStageData, self).initialize(state_args=state_args,
            outlvl=outlvl, solver=solver, optarg=optarg)

        # Free eff_isen and activate sepcial constarints
        self.efficiency_isentropic.unfix()
        self.outlet.pressure.unfix()
        self.inlet_flow_constraint.activate()
        self.isentropic_enthalpy.activate()
        self.efficiency_correlation.activate()

        slvr = SolverFactory(solver)
        slvr.options = optarg
        res = slvr.solve(self, tee=stee)

        if outlvl > 0:
            if res.solver.termination_condition == TerminationCondition.optimal:
                _log.info("{} Initialization Complete.".format(self.name))
            else:
                _log.warning(
"""{} Initialization Failed. The most likely cause of initialization failure for
the Turbine inlet stages model is that the flow coefficient is not compatible
with flow rate guess.""".format(self.name))

        # reload original spec
        from_json(self, sd=istate, wts=sp)
コード例 #20
0
ファイル: vg.py プロジェクト: dthierry/dae_flattener
class PropertyBlockData(PropertyBlockDataBase):
    """
    Example property package for reactions

    This package contains the necessary property calculations to
    demonstrte the basic unit reactor models.

    System involeves six components (a,b,c,d,e and f) involved in
    three reactions (labled 1, 2 and 3). Reactions equations are:

    a + 2b <-> c + d
    a + 2c <-> 2e
    a + b  <-> f

    Reactions are assumed to be aqueous and only a liquid phase is
    considered.

    Properties supported:
        - stoichiometric coefficients
        - rate of reaction
            - rate coefficients (forward and reverse)
            - equilibrium coefficients
        - heats of reaction
        - specific enthalpy of the fluid mixture

    """
    def build(self):
        """
        Callable method for Block construction
        """
        super(PropertyBlockData, self).build()
        self._make_params()
        self._make_vars()
        self._make_constraints()
        self._make_balance_terms()

    def _make_params(self):
        '''
        This section makes references to the necessary parameters contained
        within the Property Parameter Block provided.
        '''
        # List of valid phases in property package
        add_object_ref(self, "phase_list", self.config.parameters.phase_list)

        # Component list - a list of component identifiers
        add_object_ref(self, "component_list",
                       self.config.parameters.component_list)

        # Reaction indices - a list of identifiers for each reaction
        add_object_ref(self, "rate_reaction_idx",
                       self.config.parameters.rate_reaction_idx)

        # Mixture heat capacity
        add_object_ref(self, "cp_mol", self.config.parameters.cp_mol)

        # Stoichiometric coefficients
        add_object_ref(self, "rate_reaction_stoichiometry",
                       self.config.parameters.rate_reaction_stoichiometry)

        # Gas constant
        add_object_ref(self, "gas_const", self.config.parameters.gas_const)

        # Thermodynamic reference state
        add_object_ref(self, "temperature_ref",
                       self.config.parameters.temperature_ref)

    def _make_vars(self):
        # Create state variables
        self.flow_mol_comp = Var(self.component_list,
                                 domain=Reals,
                                 initialize=0.0,
                                 bounds=(0, 1e3),
                                 doc='Component molar flowrate [mol/s]')
        self.flow_mol = Var(domain=Reals,
                            initialize=0.0,
                            bounds=(1e-2, 1e3),
                            doc='Total molar flowrate [mol/s]')
        self.pressure = Var(domain=Reals,
                            initialize=101325.0,
                            doc='State pressure [Pa]')
        self.temperature = Var(domain=Reals,
                               initialize=303.15,
                               doc='State temperature [K]')
        self.mole_frac = Var(self.component_list,
                             domain=Reals,
                             initialize=0.0,
                             bounds=(0.0, 1.0),
                             doc='State component mole fractions [-]')
        self.enth_mol = Var(domain=Reals,
                            initialize=0.0,
                            doc='Mixture specific entahlpy [J/mol]')

    def _make_constraints(self):
        # Calcuate total flow
        self.sum_comp_flows = Constraint(expr=self.flow_mol == sum(
            self.flow_mol_comp[k] for k in self.component_list))

        # Calculate mole fractions
        def mole_fraction_calculation(b, j):
            return b.flow_mol_comp[j] == b.mole_frac[j] * b.flow_mol

        self.mole_fraction_calculation = Constraint(
            self.component_list,
            doc="Mole fraction calculation",
            rule=mole_fraction_calculation)

        # Mixture enthalpy flow
        ''' The mixture enthalpy is assumed to be equal to that of pure
            water in the liquid state, with a constant heat capacity'''
        self.enth_mol_correlation = Constraint(
            expr=self.enth_mol == self.cp_mol() *
            (self.temperature - self.temperature_ref()) -
            self.mole_frac['d'] * self.dh_rxn_mol[1] -
            0.5 * self.mole_frac['e'] * self.dh_rxn_mol[2] -
            self.mole_frac['f'] * self.dh_rxn_mol[3])

    def _dens_mol_phase(self):
        # Molar density
        self.dens_mol_phase = Var(self.phase_list,
                                  doc="Molar density [mol/m^3]")

        def dens_mol_phase_correlation(b, p):
            return b.dens_mol_phase[p] == 55555.0

        self.dens_mol_phase_correlation = Constraint(
            self.phase_list,
            doc="Molar density correlation",
            rule=dens_mol_phase_correlation)

    def _flow_vol(self):
        # Volumetric flowrate
        self.flow_vol = Var(doc="Total volumetric flowrate of material "
                            "[m^3/s]")

        def flow_vol_correlation(b):
            return b.flow_vol * b.dens_mol_phase['Liq'] == b.flow_mol

        self.flow_vol_correlation = Constraint(
            doc="Volumetric flowrate correlation", rule=flow_vol_correlation)

    def _dh_rxn_mol(self):
        # Heat of reaction
        self.dh_rxn_mol = Var(self.rate_reaction_idx,
                              domain=Reals,
                              initialize=0.0,
                              doc='Heats of Reaction [J/mol]')

        def dh_rxn_mol_constraint(b, i):
            if i == 1:
                return b.dh_rxn_mol[i] == 60000
            elif i == 2:
                return b.dh_rxn_mol[i] == 50000
            else:
                return b.dh_rxn_mol[i] == 80000

        self.dh_rxn_mol_constraint = Constraint(
            self.rate_reaction_idx,
            doc="Heat of reaction constraint",
            rule=dh_rxn_mol_constraint)

    def _reaction_rate(self):
        # Reaction rate
        self.reaction_rate = Var(self.rate_reaction_idx,
                                 domain=Reals,
                                 initialize=0.0,
                                 doc='Normalised Rate of Reaction [mol/m^3.s]')

        def rate_expressions(b, j):
            if j == 1:
                return b.reaction_rate[j] == (
                    b.k_rxn_for[j] * (b.mole_frac['a']) *
                    (b.mole_frac['b']**2) -
                    b.k_rxn_back[j] * b.mole_frac['c'] * b.mole_frac['d'])
            elif j == 2:
                return b.reaction_rate[j] == (
                    b.k_rxn_for[j] * (b.mole_frac['a']) *
                    (b.mole_frac['c']**2) - b.k_rxn_back[j] * b.mole_frac['e'])
            else:
                return b.reaction_rate[j] == (
                    b.k_rxn_for[j] * (b.mole_frac['a']) * (b.mole_frac['b']) -
                    b.k_rxn_back[j] * b.mole_frac['f'])

        try:
            # Try to build constraint
            self.rate_expressions = Constraint(
                self.rate_reaction_idx,
                doc="Rate of reaction expressions",
                rule=rate_expressions)
        except AttributeError:
            # If constraint fails, clean up so that DAE can try again later
            self.del_component(self.reaction_rate)
            self.del_component(self.rate_expressions)
            raise

    def _k_rxn_for(self):
        # Forward rate constants
        self.k_rxn_for = Var(self.rate_reaction_idx,
                             doc='Rate coefficient for forward reaction')

        # Arhenius expression for rate coefficients
        def arrhenius_expression(b, i):
            if i == 1:
                return b.k_rxn_for[i] == (17.7 *
                                          exp(-12000 /
                                              (b.gas_const() * b.temperature)))
            elif i == 2:
                return b.k_rxn_for[i] == (1.49 *
                                          exp(-7000 /
                                              (b.gas_const() * b.temperature)))
            else:
                return b.k_rxn_for[i] == (26.5 *
                                          exp(-13000 /
                                              (b.gas_const() * b.temperature)))

        try:
            # Try to build constraint
            self.arrhenius_expression = Constraint(
                self.rate_reaction_idx,
                doc="Arrhenius expression for forward rate constant",
                rule=arrhenius_expression)
        except AttributeError:
            # If constraint fails, clean up so that DAE can try again later
            self.del_component(self.k_rxn_for)
            self.del_component(self.arrhenius_expression)
            raise

    def _k_rxn_back(self):
        # Reverse rate constants
        self.k_rxn_back = Var(self.rate_reaction_idx,
                              doc='Rate coefficient for reverse reaction')

        # Reverse reaction rates coefficients in terms of forward
        # coefficients and equilibrium coefficient
        def rule_rate_const_rev(b, i):
            return b.k_rxn_for[i] == (b.k_rxn_back[i] * b.k_eq[i])

        try:
            # Try to build constraint
            self.rate_const_relationship = Constraint(
                self.rate_reaction_idx,
                doc="Relationship between forward and reverse rate constants",
                rule=rule_rate_const_rev)
        except AttributeError:
            # If constraint fails, clean up so that DAE can try again later
            self.del_component(self.k_rxn_back)
            self.del_component(self.rate_const_relationship)
            raise

    def _k_eq(self):
        # Equilibrium coefficients
        self.k_eq = Var(self.rate_reaction_idx,
                        initialize=1.0,
                        doc='Equilibrium coefficient')

        # Equilibrium coefficients as a function of temperature using the
        # van't Hoff equation'''
        def vant_hoff(b, i):
            if i == 1:
                return log(b.k_eq[i]) - log(20) == (
                    -(b.dh_rxn_mol[i] / b.gas_const()) *
                    (b.temperature**-1 - 1 / 298.15))
            elif i == 2:
                return log(b.k_eq[i]) - log(5) == (
                    -(b.dh_rxn_mol[i] / b.gas_const()) *
                    (b.temperature**-1 - 1 / 298.15))
            else:
                return log(b.k_eq[i]) - log(10) == (
                    -(b.dh_rxn_mol[i] / b.gas_const()) *
                    (b.temperature**-1 - 1 / 298.15))

        try:
            # Try to build constraint
            self.vant_hoff = Constraint(
                self.rate_reaction_idx,
                doc="Van't Hoff equation for equilibrium",
                rule=vant_hoff)
        except AttributeError:
            # If constraint fails, clean up so that DAE can try again later
            self.del_component(self.k_eq)
            self.del_component(self.vant_hoff)
            raise

    def _diffus(self):
        # Diffusion tests
        self.diffus = Var(self.phase_list,
                          self.component_list,
                          domain=Reals,
                          doc="Diffusion coefficient [m^2/s]")
        self.diffus.fix(1e-2)

    def _therm_cond(self):
        self.therm_cond = Var(self.phase_list,
                              domain=Reals,
                              doc="Thermal conductivity [W/m.K]")
        self.therm_cond.fix(10)

    def _material_concentration_term(self):
        self.material_concentration_term = Var(
            self.phase_list,
            self.component_list,
            domain=Reals,
            doc="Concentration for diffusion")

        def material_concentration_calc(b, k, j):
            return b.material_concentration_term[k, j] == (25 * b.mole_frac[j])

        self.material_concentration_calc = Constraint(
            self.phase_list,
            self.component_list,
            doc="Molar concentration calculation",
            rule=material_concentration_calc)

    def _make_balance_terms(self):
        def material_balance_term(b, i, j):
            return b.flow_mol_comp[j]

        self.material_balance_term = Expression(self.phase_list,
                                                self.component_list,
                                                rule=material_balance_term)

        def energy_balance_term(b, i):
            return b.enth_mol * b.flow_mol

        self.energy_balance_term = Expression(self.phase_list,
                                              rule=energy_balance_term)

        def material_density_term(b, p, j):
            return b.dens_mol_phase[p] * b.mole_frac[j]

        self.material_density_term = Expression(self.phase_list,
                                                self.component_list,
                                                rule=material_density_term)

        def energy_density_term(b, p):
            return b.dens_mol_phase[p] * b.enth_mol

        self.energy_density_term = Expression(self.phase_list,
                                              rule=energy_density_term)

    def declare_port_members(b):
        members = {
            "flow_mol_comp": b.flow_mol_comp,
            "enth_mol": b.enth_mol,
            "pressure": b.pressure
        }
        return members

    def model_check(blk):
        '''Method containing presovle checks for package'''
        pass
コード例 #21
0
class PHEData(UnitModelBlockData):
    """Plate Heat Exchanger(PHE) Unit Model."""

    CONFIG = UnitModelBlockData.CONFIG()

    # Configuration template for fluid specific  arguments
    _SideCONFIG = ConfigBlock()

    CONFIG.declare(
        "passes",
        ConfigValue(
            default=4,
            domain=int,
            description="Number of passes",
            doc="""Number of passes of the fluids through the heat exchanger"""
        ))

    CONFIG.declare(
        "channel_list",
        ConfigValue(
            default=[12, 12, 12, 12],
            domain=list,
            description="Number of channels for each pass",
            doc="""Number of channels to be used in each pass where a channel
               is the space between two plates with a flowing fluid"""))

    CONFIG.declare(
        "divider_plate_number",
        ConfigValue(
            default=0,
            domain=int,
            description="Number of divider plates in heat exchanger",
            doc=
            """Divider plates are used to create separate partitions in the unit.
               Each pass can be separated by a divider plate"""))

    CONFIG.declare(
        "port_diameter",
        ConfigValue(
            default=0.2045,
            domain=float,
            description="Diameter of the ports on the plate [m]",
            doc="""Diameter of the ports on the plate for fluid entry/exit
               into a channel"""))

    CONFIG.declare(
        "plate_thermal_cond",
        ConfigValue(
            default=16.2,
            domain=float,
            description="Thermal conductivity [W/m.K]",
            doc="""Thermal conductivity of the plate material [W/m.K]"""))

    CONFIG.declare(
        "total_area",
        ConfigValue(
            default=114.3,
            domain=float,
            description="Total heat transfer area [m2]",
            doc="""Total heat transfer area as specifed by the manufacturer""")
    )

    CONFIG.declare(
        "plate_thickness",
        ConfigValue(default=0.0006,
                    domain=float,
                    description="Plate thickness [m]",
                    doc="""Plate thickness"""))

    CONFIG.declare(
        "plate_vertical_dist",
        ConfigValue(
            default=1.897,
            domain=float,
            description="Vertical distance between centers of ports [m].",
            doc=
            """Vertical distance between centers of ports.(Top and bottom ports)
            (approximately equals to the plate length)"""))

    CONFIG.declare(
        "plate_horizontal_dist",
        ConfigValue(
            default=0.409,
            domain=float,
            description="Horizontal distance between centers of ports [m].",
            doc=
            """Horizontal distance between centers of ports(Left and right ports)"""
        ))

    CONFIG.declare(
        "plate_pact_length",
        ConfigValue(default=0.381,
                    domain=float,
                    description="Compressed plate pact length [m].",
                    doc="""Compressed plate pact length.
               Length between the Head and the Follower"""))

    CONFIG.declare(
        "surface_enlargement_factor",
        ConfigValue(
            default=None,
            domain=float,
            description="Surface enlargement factor",
            doc="""Surface enlargement factor is the ratio of single plate area
               (obtained from the total area) to the projected plate area"""))

    CONFIG.declare(
        "plate_gap",
        ConfigValue(
            default=None,
            domain=float,
            description="Mean channel spacing or gap bewteen two plates [m]",
            doc="""The plate gap is the distance between two adjacent plates that
               forms a flow channel """))

    _SideCONFIG.declare(
        "property_package",
        ConfigValue(
            default=useDefault,
            domain=is_physical_parameter_block,
            description="Property package to use ",
            doc="""Property parameter object used to define property calculations
        **default** - useDefault.
        **Valid values:** {
        **useDefault** - use default package from parent model or flowsheet,
        **PhysicalParameterObject** - a PhysicalParameterBlock object.}"""))

    _SideCONFIG.declare(
        "property_package_args",
        ConfigBlock(
            implicit=True,
            description="Arguments to use for constructing property package",
            doc="""A ConfigBlock with arguments to be passed to
        property block(s) and used when constructing these,
        **default** - None.
        **Valid values:** {
        see property package for documentation.}"""))

    # Create individual config blocks for hot and cold sides
    CONFIG.declare("hot_side", _SideCONFIG(doc="Hot fluid config arguments"))
    CONFIG.declare("cold_side", _SideCONFIG(doc="Cold fluid config arguments"))

    def build(self):
        # Call UnitModel.build to setup model
        super(PHEData, self).build()

        # Consistency check for number of passes and channels in each pass
        for i in self.config.channel_list:
            if not isinstance(i, int):
                raise ConfigurationError("number of channels ({}) must be"
                                         " an integer".format(i))

        if (self.config.passes != len(self.config.channel_list)):
            raise ConfigurationError(
                "The number of elements in the channel list: {}  "
                " does not match the number of passes ({}) given. "
                "Please provide as integers, the number of channels of each pass"
                .format(self.config.channel_list, self.config.passes))

        # ======================================================================
        # Build hot-side  Control Volume (Lean Solvent)
        self.hot_side = ControlVolume0DBlock(
            default={
                "dynamic": self.config.dynamic,
                "has_holdup": self.config.has_holdup,
                "property_package": self.config.hot_side.property_package,
                "property_package_args":
                self.config.hot_side.property_package_args
            })

        self.hot_side.add_state_blocks(has_phase_equilibrium=False)

        self.hot_side.add_material_balances(
            balance_type=MaterialBalanceType.componentTotal,
            has_mass_transfer=False,
            has_phase_equilibrium=False,
            has_rate_reactions=False)

        self.hot_side.add_momentum_balances(
            balance_type=MomentumBalanceType.pressureTotal,
            has_pressure_change=True)

        # Energy balance is based on the effectiveness Number of Transfer units
        # (E-NTU method) and inluded as performance equations. Hence the control
        #  volume energy balances are not added.

        # ======================================================================
        # Build cold-side  Control Volume(Rich solvent)
        self.cold_side = ControlVolume0DBlock(
            default={
                "dynamic":
                self.config.dynamic,
                "has_holdup":
                self.config.has_holdup,
                "property_package":
                self.config.cold_side.property_package,
                "property_package_args":
                self.config.cold_side.property_package_args
            })

        self.cold_side.add_state_blocks(has_phase_equilibrium=False)

        self.cold_side.add_material_balances(
            balance_type=MaterialBalanceType.componentTotal,
            has_mass_transfer=False,
            has_phase_equilibrium=False,
            has_rate_reactions=False)

        self.cold_side.add_momentum_balances(
            balance_type=MomentumBalanceType.pressureTotal,
            has_pressure_change=True)

        # ======================================================================
        # Add Ports to control volumes
        # hot-side
        self.add_inlet_port(name="hot_inlet",
                            block=self.hot_side,
                            doc='inlet Port')
        self.add_outlet_port(name="hot_outlet",
                             block=self.hot_side,
                             doc='outlet Port')

        # cold-side
        self.add_inlet_port(name="cold_inlet",
                            block=self.cold_side,
                            doc='inlet Port')
        self.add_outlet_port(name="cold_outlet",
                             block=self.cold_side,
                             doc='outlet Port')
        # ======================================================================
        # Add performace equation method
        self._make_params()
        self._make_performance_method()

    def _make_params(self):
        self.P = Param(initialize=self.config.passes,
                       units=None,
                       doc="Total number of passes for hot or cold fluid")
        self.PH = RangeSet(self.P, doc="Set of hot fluid passes")
        self.PC = RangeSet(self.P, doc="Set of cold fluid passes(equal to PH)")

        self.plate_thermal_cond = Param(
            mutable=True,
            initialize=self.config.plate_thermal_cond,
            units=pyunits.W / pyunits.m / pyunits.K,
            doc="Plate thermal conductivity")
        self.plate_thick = Param(mutable=True,
                                 initialize=self.config.plate_thickness,
                                 units=pyunits.m,
                                 doc="Plate thickness")

        self.port_dia = Param(mutable=True,
                              initialize=self.config.port_diameter,
                              units=pyunits.m,
                              doc=" Port diameter of plate ")
        self.Np = Param(self.PH,
                        units=None,
                        doc="Number of channels in each pass",
                        mutable=True)
        # Number of channels in each pass
        for i in self.PH:
            self.Np[i].value = self.config.channel_list[i - 1]

        # ---------------------------------------------------------------------
        # Assign plate specifications

        # effective plate length & width
        _effective_plate_length = self.config.plate_vertical_dist - \
            self.config.port_diameter
        _effective_plate_width = self.config.plate_horizontal_dist + \
            self.config.port_diameter

        self.plate_length = Expression(expr=_effective_plate_length)
        self.plate_width = Expression(expr=_effective_plate_width)

        # Area of single plate
        _total_active_plate_number = 2 * sum(self.config.channel_list) - 1 -\
            self.config.divider_plate_number

        self.plate_area = Expression(expr=self.config.total_area /
                                     _total_active_plate_number,
                                     doc="Heat transfer area of single plate")

        # Plate gap
        if self.config.plate_gap is None:
            _total_plate_number = 2 * sum(self.config.channel_list) + 1 +\
                self.config.divider_plate_number
            _plate_pitch = self.config.plate_pact_length / _total_plate_number

            _plate_gap = _plate_pitch - self.config.plate_thickness
        else:
            _plate_gap = self.config.plate_gap

        self.plate_gap = Expression(expr=_plate_gap)

        # Surface enlargement factor
        if self.config.surface_enlargement_factor is None:
            _projected_plate_area = _effective_plate_length * _effective_plate_width
            _surface_enlargement_factor = self.plate_area / _projected_plate_area
        else:
            _surface_enlargement_factor = self.config.surface_enlargement_factor

        self.surface_enlargement_factor = Expression(
            expr=_surface_enlargement_factor)

        # Channel equivalent diameter
        self.channel_dia = Expression(expr=2 * self.plate_gap /
                                      _surface_enlargement_factor,
                                      doc=" Channel equivalent diameter")

        # heat transfer parameters
        self.param_a = Var(initialize=0.3,
                           bounds=(0.2, 0.4),
                           units=None,
                           doc='Nusselt parameter')
        self.param_b = Var(initialize=0.663,
                           bounds=(0.3, 0.7),
                           units=None,
                           doc='Nusselt parameter')
        self.param_c = Var(initialize=1 / 3.0,
                           bounds=(1e-5, 2),
                           units=None,
                           doc='Nusselt parameter')
        self.param_a.fix(0.4)
        self.param_b.fix(0.663)
        self.param_c.fix(0.333)

    def _make_performance_method(self):

        solvent_list = self.config.hot_side.property_package.component_list_solvent

        def rule_trh(blk, t):
            return (blk.hot_side.properties_out[t].temperature /
                    blk.hot_side.properties_in[t].temperature)

        self.trh = Expression(self.flowsheet().config.time,
                              rule=rule_trh,
                              doc='Ratio of hot outlet temperature to hot'
                              'inlet temperature')

        def rule_trc(blk, t):
            return (blk.cold_side.properties_out[t].temperature /
                    blk.cold_side.properties_in[t].temperature)

        self.trc = Expression(self.flowsheet().config.time,
                              rule=rule_trc,
                              doc='Ratio of cold outlet temperature to cold'
                              ' inlet temperature')

        def rule_cp_comp_hot(blk, t, j):
            return 1e3 * (
                blk.hot_side.properties_in[t]._params.cp_param[j, 1] +
                blk.hot_side.properties_in[t]._params.cp_param[j, 2] / 2 *
                blk.hot_side.properties_in[t].temperature * (blk.trh[t] + 1) +
                blk.hot_side.properties_in[t]._params.cp_param[j, 3] / 3 *
                (blk.hot_side.properties_in[t].temperature**2) *
                (blk.trh[t]**2 + blk.trh[t] + 1) +
                blk.hot_side.properties_in[t]._params.cp_param[j, 4] / 4 *
                (blk.hot_side.properties_in[t].temperature**3) *
                (blk.trh[t] + 1) * (blk.trh[t]**2 + 1) +
                blk.hot_side.properties_in[t]._params.cp_param[j, 5] / 5 *
                (blk.hot_side.properties_in[t].temperature**4) *
                (blk.trh[t]**4 + blk.trh[t]**3 + blk.trh[t]**2 + blk.trh[t] +
                 1))

        self.cp_comp_hot = Expression(
            self.flowsheet().config.time,
            solvent_list,
            rule=rule_cp_comp_hot,
            doc='Component mean specific heat capacity'
            ' btw inlet and outlet'
            ' of hot-side temperature')

        def rule_cp_hot(blk, t):
            return sum(blk.cp_comp_hot[t, j] *
                       blk.hot_side.properties_in[t].mass_frac_co2_free[j]
                       for j in solvent_list)

        self.cp_hot = Expression(self.flowsheet().config.time,
                                 rule=rule_cp_hot,
                                 doc='Hot-side mean specific heat capacity on'
                                 'free CO2 basis')

        def rule_cp_comp_cold(blk, t, j):
            return 1e3 * (
                blk.cold_side.properties_in[t]._params.cp_param[j, 1] +
                blk.cold_side.properties_in[t]._params.cp_param[j, 2] / 2 *
                blk.cold_side.properties_in[t].temperature * (blk.trc[t] + 1) +
                blk.cold_side.properties_in[t]._params.cp_param[j, 3] / 3 *
                (blk.cold_side.properties_in[t].temperature**2) *
                (blk.trc[t]**2 + blk.trc[t] + 1) +
                blk.cold_side.properties_in[t]._params.cp_param[j, 4] / 4 *
                (blk.cold_side.properties_in[t].temperature**3) *
                (blk.trc[t] + 1) * (blk.trc[t]**2 + 1) +
                blk.cold_side.properties_in[t]._params.cp_param[j, 5] / 5 *
                (blk.cold_side.properties_in[t].temperature**4) *
                (blk.trc[t]**4 + blk.trc[t]**3 + blk.trc[t]**2 + blk.trc[t] +
                 1))

        self.cp_comp_cold = Expression(
            self.flowsheet().config.time,
            solvent_list,
            rule=rule_cp_comp_cold,
            doc='Component mean specific heat capacity'
            'btw inlet and outlet'
            ' of cold-side temperature')

        def rule_cp_cold(blk, t):
            return sum(blk.cp_comp_cold[t, j] *
                       blk.cold_side.properties_in[t].mass_frac_co2_free[j]
                       for j in solvent_list)

        self.cp_cold = Expression(self.flowsheet().config.time,
                                  rule=rule_cp_cold,
                                  doc='Cold-side mean specific heat capacity'
                                  'on free CO2 basis')

        # Model Variables
        self.Th_in = Var(self.flowsheet().config.time,
                         self.PH,
                         initialize=393,
                         units=pyunits.K,
                         doc="Hot Temperature IN of pass")
        self.Th_out = Var(self.flowsheet().config.time,
                          self.PH,
                          initialize=325,
                          units=pyunits.K,
                          doc="Hot Temperature OUT of pass")
        self.Tc_in = Var(self.flowsheet().config.time,
                         self.PH,
                         initialize=320,
                         units=pyunits.K,
                         doc="Cold Temperature IN of pass")
        self.Tc_out = Var(self.flowsheet().config.time,
                          self.PH,
                          initialize=390,
                          units=pyunits.K,
                          doc="Cold Temperature OUT of pass")

        # ======================================================================
        # PERFORMANCE EQUATIONS
        # mass flow rate in kg/s
        def rule_mh_in(blk, t):
            return blk.hot_side.properties_in[t].flow_mol *\
                blk.hot_side.properties_in[t].mw

        self.mh_in = Expression(self.flowsheet().config.time,
                                rule=rule_mh_in,
                                doc='Hotside mass flow rate [kg/s]')

        def rule_mc_in(blk, t):
            return blk.cold_side.properties_in[t].flow_mol *\
                blk.cold_side.properties_in[t].mw

        self.mc_in = Expression(self.flowsheet().config.time,
                                rule=rule_mc_in,
                                doc='Coldside mass flow rate [kg/s]')

        # ----------------------------------------------------------------------
        # port mass velocity[kg/m2.s]
        def rule_Gph(blk, t):
            return (4 * blk.mh_in[t] * 7) / (22 * blk.port_dia**2)

        self.Gph = Expression(self.flowsheet().config.time,
                              rule=rule_Gph,
                              doc='Hotside port mass velocity[kg/m2.s]')

        def rule_Gpc(blk, t):
            return (4 * blk.mc_in[t] * 7) / (22 * blk.port_dia**2)

        self.Gpc = Expression(self.flowsheet().config.time,
                              rule=rule_Gpc,
                              doc='Coldside port mass velocity[kg/m2.s]')

        # ----------------------------------------------------------------------
        # Reynold & Prandtl numbers
        def rule_Re_h(blk, t, p):
            return blk.mh_in[t] * blk.channel_dia /\
                (blk.Np[p] * blk.plate_width *
                 blk.plate_gap * blk.hot_side.properties_in[t].visc_d)

        self.Re_h = Expression(self.flowsheet().config.time,
                               self.PH,
                               rule=rule_Re_h,
                               doc='Hotside Reynolds number')

        def rule_Re_c(blk, t, p):
            return blk.mc_in[t] * blk.channel_dia /\
                (blk.Np[p] * blk.plate_width *
                 blk.plate_gap * blk.cold_side.properties_in[t].visc_d)

        self.Re_c = Expression(self.flowsheet().config.time,
                               self.PH,
                               rule=rule_Re_c,
                               doc='Coldside Reynolds number')

        def rule_Pr_h(blk, t):
            return blk.cp_hot[t] * blk.hot_side.properties_in[t].visc_d /\
                blk.hot_side.properties_in[t].thermal_cond

        self.Pr_h = Expression(self.flowsheet().config.time,
                               rule=rule_Pr_h,
                               doc='Hotside Prandtl number')

        def rule_Pr_c(blk, t):
            return blk.cp_cold[t] * blk.cold_side.properties_in[t].visc_d /\
                blk.cold_side.properties_in[t].thermal_cond

        self.Pr_c = Expression(self.flowsheet().config.time,
                               rule=rule_Pr_c,
                               doc='Coldside Prandtl number')

        # ----------------------------------------------------------------------
        # Film heat transfer coefficients

        def rule_hotside_transfer_coef(blk, t, p):
            return (blk.hot_side.properties_in[t].thermal_cond /
                    blk.channel_dia * blk.param_a *
                    blk.Re_h[t, p]**blk.param_b * blk.Pr_h[t]**blk.param_c)

        self.h_hot = Expression(self.flowsheet().config.time,
                                self.PH,
                                rule=rule_hotside_transfer_coef,
                                doc='Hotside heat transfer coefficient')

        def rule_coldside_transfer_coef(blk, t, p):
            return (blk.cold_side.properties_in[t].thermal_cond /
                    blk.channel_dia * blk.param_a *
                    blk.Re_c[t, p]**blk.param_b * blk.Pr_c[t]**blk.param_c)

        self.h_cold = Expression(self.flowsheet().config.time,
                                 self.PH,
                                 rule=rule_coldside_transfer_coef,
                                 doc='Coldside heat transfer coefficient')

        # ----------------------------------------------------------------------
        # Friction factor calculation
        def rule_fric_h(blk, t):
            return 18.29 * blk.Re_h[t, 1]**(-0.652)

        self.fric_h = Expression(self.flowsheet().config.time,
                                 rule=rule_fric_h,
                                 doc='Hotside friction factor')

        def rule_fric_c(blk, t):
            return 1.441 * self.Re_c[t, 1]**(-0.206)

        self.fric_c = Expression(self.flowsheet().config.time,
                                 rule=rule_fric_c,
                                 doc='Coldside friction factor')

        # ----------------------------------------------------------------------
        # pressure drop calculation
        def rule_hotside_dP(blk, t):
            return (2 * blk.fric_h[t] * (blk.plate_length + blk.port_dia) *
                    blk.P * blk.Gph[t]**2) /\
                (blk.hot_side.properties_in[t].dens_mass *
                 blk.channel_dia) + 1.4 * blk.P * blk.Gph[t]**2 * 0.5 /\
                blk.hot_side.properties_in[t].dens_mass + \
                blk.hot_side.properties_in[t].dens_mass * \
                9.81 * (blk.plate_length + blk.port_dia)

        self.dP_h = Expression(self.flowsheet().config.time,
                               rule=rule_hotside_dP,
                               doc='Hotside pressure drop  [Pa]')

        def rule_coldside_dP(blk, t):
            return (2 * blk.fric_c[t] * (blk.plate_length + blk.port_dia) *
                    blk.P * blk.Gpc[t]**2) /\
                (blk.cold_side.properties_in[t].dens_mass * blk.channel_dia) +\
                1.4 * (blk.P * blk.Gpc[t]**2 * 0.5 /
                       blk.cold_side.properties_in[t].dens_mass) + \
                blk.cold_side.properties_in[t].dens_mass * \
                9.81 * (blk.plate_length + blk.port_dia)

        self.dP_c = Expression(self.flowsheet().config.time,
                               rule=rule_coldside_dP,
                               doc='Coldside pressure drop  [Pa]')

        def rule_eq_deltaP_hot(blk, t):
            return blk.hot_side.deltaP[t] == -blk.dP_h[t]

        self.eq_deltaP_hot = Constraint(self.flowsheet().config.time,
                                        rule=rule_eq_deltaP_hot)

        def rule_eq_deltaP_cold(blk, t):
            return blk.cold_side.deltaP[t] == -blk.dP_c[t]

        self.eq_deltaP_cold = Constraint(self.flowsheet().config.time,
                                         rule=rule_eq_deltaP_cold)

        # ----------------------------------------------------------------------
        # Overall heat transfer coefficients
        def rule_U(blk, t, p):
            return 1.0 /\
                (1.0 / blk.h_hot[t, p] + blk.plate_gap / blk.plate_thermal_cond +
                 1.0 / blk.h_cold[t, p])

        self.U = Expression(self.flowsheet().config.time,
                            self.PH,
                            rule=rule_U,
                            doc='Overall heat transfer coefficient')

        # ----------------------------------------------------------------------
        # capacitance of hot and cold fluid

        def rule_Caph(blk, t, p):
            return blk.mh_in[t] * blk.cp_hot[t] / blk.Np[p]

        self.Caph = Expression(self.flowsheet().config.time,
                               self.PH,
                               rule=rule_Caph,
                               doc='Hotfluid capacitance rate')

        def rule_Capc(blk, t, p):
            return blk.mc_in[t] * blk.cp_cold[t] / blk.Np[p]

        self.Capc = Expression(self.flowsheet().config.time,
                               self.PH,
                               rule=rule_Capc,
                               doc='Coldfluid capacitance rate')

        # ----------------------------------------------------------------------
        # min n max capacitance and capacitance ratio
        def rule_Cmin(blk, t, p):
            return 0.5 * (blk.Caph[t, p] + blk.Capc[t, p] - (
                (blk.Caph[t, p] - blk.Capc[t, p])**2 + 0.00001)**0.5)

        self.Cmin = Expression(self.flowsheet().config.time,
                               self.PH,
                               rule=rule_Cmin,
                               doc='Minimum capacitance rate')

        def rule_Cmax(blk, t, p):
            return 0.5 * (blk.Caph[t, p] + blk.Capc[t, p] + (
                (blk.Caph[t, p] - blk.Capc[t, p])**2 + 0.00001)**0.5)

        self.Cmax = Expression(self.flowsheet().config.time,
                               self.PH,
                               rule=rule_Cmax,
                               doc='Maximum capacitance rate')

        def rule_CR(blk, t, p):
            return blk.Cmin[t, p] / blk.Cmax[t, p]

        self.CR = Expression(self.flowsheet().config.time,
                             self.PH,
                             rule=rule_CR,
                             doc='Capacitance ratio')

        # ----------------------------------------------------------------------
        # Number of Transfer units for sub heat exchanger
        def rule_NTU(blk, t, p):
            return blk.U[t, p] * blk.plate_area / blk.Cmin[t, p]

        self.NTU = Expression(self.flowsheet().config.time,
                              self.PH,
                              rule=rule_NTU,
                              doc='Number of Transfer Units')

        # ----------------------------------------------------------------------
        # effectiveness of sub-heat exchangers
        def rule_Ecf(blk, t, p):
            if blk.P.value % 2 == 0:
                return (1 - exp(-blk.NTU[t, p] * (1 - blk.CR[t, p]))) / \
                    (1 - blk.CR[t, p] *
                     exp(-blk.NTU[t, p] * (1 - blk.CR[t, p])))
            elif blk.P.value % 2 == 1:
                return (1 - exp(-blk.NTU[t, p] *
                                (1 + blk.CR[t, p]))) / (1 + blk.CR[t, p])

        self.Ecf = Expression(self.flowsheet().config.time,
                              self.PH,
                              rule=rule_Ecf,
                              doc='Effectiveness for sub-HX')

        # ----------------------------------------------------------------------
        # Energy balance equations for hot fluid in sub-heat exhanger
        def rule_Ebh_eq(blk, t, p):
            return blk.Th_out[t, p] == blk.Th_in[t, p] -\
                blk.Ecf[t, p] * blk.Cmin[t, p] / blk.Caph[t, p] * \
                (blk.Th_in[t, p] - blk.Tc_in[t, p])

        self.Ebh_eq = Constraint(
            self.flowsheet().config.time,
            self.PH,
            rule=rule_Ebh_eq,
            doc='Hot fluid sub-heat exchanger energy balance')

        # Hot fluid exit temperature
        def rule_Tout_hot(blk, t):
            return blk.Th_out[t, blk.P.value] ==\
                blk.hot_side.properties_out[t].temperature

        self.Tout_hot_eq = Constraint(self.flowsheet().config.time,
                                      rule=rule_Tout_hot,
                                      doc='Hot fluid exit temperature')

        # Energy balance equations for cold fluid in sub-heat exhanger
        def rule_Ebc_eq(blk, t, p):
            return blk.Tc_out[t, p] == blk.Tc_in[t, p] + \
                blk.Ecf[t, p] * blk.Cmin[t, p] / blk.Capc[t, p] * \
                (blk.Th_in[t, p] - blk.Tc_in[t, p])

        self.Ebc_eq = Constraint(
            self.flowsheet().config.time,
            self.PH,
            rule=rule_Ebc_eq,
            doc='Cold fluid sub-heat exchanger energy balance')

        # Cold fluid exit temperature
        def rule_Tout_cold(blk, t):
            return blk.Tc_out[t, 1] ==\
                blk.cold_side.properties_out[t].temperature

        self.Tout_cold_eq = Constraint(self.flowsheet().config.time,
                                       rule=rule_Tout_cold,
                                       doc='Cold fluid exit temperature')

        # ----------------------------------------------------------------------
        # Energy balance boundary conditions
        def rule_hot_BCIN(blk, t):
            return blk.Th_in[t, 1] == \
                blk.hot_side.properties_in[t].temperature

        self.hot_BCIN = Constraint(self.flowsheet().config.time,
                                   rule=rule_hot_BCIN,
                                   doc='Hot fluid inlet boundary conditions')

        def rule_cold_BCIN(blk, t):
            return blk.Tc_in[t, blk.P.value] ==\
                blk.cold_side.properties_in[t].temperature

        self.cold_BCIN = Constraint(self.flowsheet().config.time,
                                    rule=rule_cold_BCIN,
                                    doc='Cold fluid inlet boundary conditions')

        Pset = [i for i in range(1, self.P.value)]

        def rule_hot_BC(blk, t, p):
            return blk.Th_out[t, p] == blk.Th_in[t, p + 1]

        self.hot_BC = Constraint(
            self.flowsheet().config.time,
            Pset,
            rule=rule_hot_BC,
            doc='Hot fluid boundary conditions: change of pass')

        def rule_cold_BC(blk, t, p):
            return blk.Tc_out[t, p + 1] == blk.Tc_in[t, p]

        self.cold_BC = Constraint(
            self.flowsheet().config.time,
            Pset,
            rule=rule_cold_BC,
            doc='Cold fluid boundary conditions: change of pass')

        # ----------------------------------------------------------------------
        # Energy transferred
        def rule_QH(blk, t):
            return blk.mh_in[t] * blk.cp_hot[t] *\
                (blk.hot_side.properties_in[t].temperature -
                 blk.hot_side.properties_out[t].temperature)

        self.QH = Expression(self.flowsheet().config.time,
                             rule=rule_QH,
                             doc='Heat lost by hot fluid')

        def rule_QC(blk, t):
            return blk.mc_in[t] * blk.cp_cold[t] *\
                (blk.cold_side.properties_out[t].temperature -
                 blk.cold_side.properties_in[t].temperature)

        self.QC = Expression(self.flowsheet().config.time,
                             rule=rule_QH,
                             doc='Heat gain by cold fluid')

    def initialize(blk,
                   hotside_state_args=None,
                   coldside_state_args=None,
                   outlvl=idaeslog.NOTSET,
                   solver=None,
                   optarg=None):
        '''
        Initialisation routine for PHE unit (default solver ipopt)

        Keyword Arguments:
            state_args : a dict of arguments to be passed to the property
                           package(s) to provide an initial state for
                           initialization (see documentation of the specific
                           property package) (default = {}).
            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)
        Returns:
            None
        '''
        # Set solver options
        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)

        hotside_state_args = {
            'flow_mol': value(blk.hot_inlet.flow_mol[0]),
            'temperature': value(blk.hot_inlet.temperature[0]),
            'pressure': value(blk.hot_inlet.pressure[0]),
            'mole_frac_comp': {
                'H2O': value(blk.hot_inlet.mole_frac_comp[0, 'H2O']),
                'CO2': value(blk.hot_inlet.mole_frac_comp[0, 'CO2']),
                'MEA': value(blk.hot_inlet.mole_frac_comp[0, 'MEA'])
            }
        }

        coldside_state_args = {
            'flow_mol': value(blk.cold_inlet.flow_mol[0]),
            'temperature': value(blk.cold_inlet.temperature[0]),
            'pressure': value(blk.cold_inlet.pressure[0]),
            'mole_frac_comp': {
                'H2O': value(blk.cold_inlet.mole_frac_comp[0, 'H2O']),
                'CO2': value(blk.cold_inlet.mole_frac_comp[0, 'CO2']),
                'MEA': value(blk.cold_inlet.mole_frac_comp[0, 'MEA'])
            }
        }

        # ---------------------------------------------------------------------
        # Initialize the INLET properties
        init_log.info('STEP 1: PROPERTY INITIALIZATION')
        init_log.info_high("INLET Properties initialization")
        blk.hot_side.properties_in.initialize(state_args=hotside_state_args,
                                              outlvl=outlvl,
                                              optarg=optarg,
                                              solver=solver,
                                              hold_state=True)
        blk.cold_side.properties_in.initialize(state_args=coldside_state_args,
                                               outlvl=outlvl,
                                               optarg=optarg,
                                               solver=solver,
                                               hold_state=True)

        # Initialize the OUTLET properties
        init_log.info_high("OUTLET Properties initialization")
        blk.hot_side.properties_out.initialize(state_args=hotside_state_args,
                                               outlvl=outlvl,
                                               optarg=optarg,
                                               solver=solver,
                                               hold_state=False)

        blk.cold_side.properties_out.initialize(state_args=coldside_state_args,
                                                outlvl=outlvl,
                                                optarg=optarg,
                                                solver=solver,
                                                hold_state=False)
        # ----------------------------------------------------------------------
        init_log.info('STEP 2: PHE INITIALIZATION')
        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = opt.solve(blk, tee=slc.tee)
        init_log.info_high("STEP 2 Complete: {}.".format(
            idaeslog.condition(res)))
        init_log.info('INITIALIZATION COMPLETED')
コード例 #22
0
class SteamValveData(PressureChangerData):
    # Same settings as the default pressure changer, but force to expander with
    # isentropic efficiency
    CONFIG = PressureChangerData.CONFIG()
    _define_config(CONFIG)

    def build(self):
        super().build()

        self.valve_opening = Var(self.flowsheet().config.time,
                                 initialize=1,
                                 doc="Fraction open for valve from 0 to 1")
        self.Cv = Var(initialize=0.1,
                      doc="Valve flow coefficent, for vapor "
                      "[mol/s/Pa] for liquid [mol/s/Pa^0.5]")
        self.flow_scale = Param(
            mutable=True,
            default=1e3,
            doc=
            "Scaling factor for pressure flow relation should be approximatly"
            " the same order of magnitude as the expected flow.")
        self.Cv.fix()
        self.valve_opening.fix()

        # set up the valve function rule.  I'm not sure these matter too much
        # for us, but the options are easy enough to provide.
        if self.config.valve_function == ValveFunctionType.linear:
            rule = _linear_rule
        elif self.config.valve_function == ValveFunctionType.quick_opening:
            rule = _quick_open_rule
        elif self.config.valve_function == ValveFunctionType.equal_percentage:
            self.alpha = Var(initialize=1, doc="Valve function parameter")
            self.alpha.fix()
            rule = equal_percentage_rule
        else:
            rule = self.config.valve_function_rule

        self.valve_function = Expression(self.flowsheet().config.time,
                                         rule=rule,
                                         doc="Valve function expression")

        if self.config.phase == "Liq":
            rule = _liquid_pressure_flow_rule
        else:
            rule = _vapor_pressure_flow_rule

        self.pressure_flow_equation = Constraint(self.flowsheet().config.time,
                                                 rule=rule)

    def initialize(self,
                   state_args={},
                   outlvl=0,
                   solver='ipopt',
                   optarg={
                       'tol': 1e-6,
                       'max_iter': 30
                   }):
        """
        Initialize the turbine stage model.  This deactivates the
        specialized constraints, then does the isentropic turbine initialization,
        then reactivates the constraints and solves.

        Args:
            state_args (dict): Initial state for property initialization
            outlvl (int): Amount of output (0 to 3) 0 is lowest
            solver (str): Solver to use for initialization
            optarg (dict): Solver arguments dictionary
        """
        stee = True if outlvl >= 3 else False
        # sp is what to save to make sure state after init is same as the start
        #   saves value, fixed, and active state, doesn't load originally free
        #   values, this makes sure original problem spec is same but initializes
        #   the values of free vars
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)

        self.deltaP[:].unfix()
        self.ratioP[:].unfix()

        # fix inlet and free outlet
        for t in self.flowsheet().config.time:
            for k, v in self.inlet.vars.items():
                v[t].fix()
            for k, v in self.outlet.vars.items():
                v[t].unfix()
            # to calculate outlet pressure
            Pout = self.outlet.pressure[t]
            Pin = self.inlet.pressure[t]
            if self.deltaP[t].value is not None:
                prdp = value((self.deltaP[t] - Pin) / Pin)
            else:
                prdp = -100  # crazy number to say don't use deltaP as guess
            if value(Pout / Pin) > 1 or value(Pout / Pin) < 0.0:
                if value(self.ratioP[t]) <= 1 and value(self.ratioP[t]) >= 0:
                    Pout.value = value(Pin * self.ratioP[t])
                elif prdp <= 1 and prdp >= 0:
                    Pout.value = value(prdp * Pin)
                else:
                    Pout.value = value(Pin * 0.95)
            self.deltaP[t] = value(Pout - Pin)
            self.ratioP[t] = value(Pout / Pin)

        # Make sure the initialization problem has no degrees of freedom
        # This shouldn't happen here unless there is a bug in this
        dof = degrees_of_freedom(self)
        try:
            assert (dof == 0)
        except:
            _log.exception("degrees_of_freedom = {}".format(dof))
            raise

        # one bad thing about reusing this is that the log messages aren't
        # really compatible with being nested inside another initialization
        super().initialize(state_args=state_args,
                           outlvl=outlvl,
                           solver=solver,
                           optarg=optarg)

        # reload original spec
        from_json(self, sd=istate, wts=sp)
コード例 #23
0
ファイル: helmholtz.py プロジェクト: bhattade/idaes-pse
class HelmholtzStateBlockData(StateBlockData):
    """
    This is a base clase for Helmholtz equations of state using IDAES standard
    Helmholtz EOS external functions written in C++.
    """

    def initialize(self, *args, **kwargs):
        # With this particualr property pacakage there is not need for
        # initialization
        pass

    def _external_functions(self):
        """Create ExternalFunction components.  This includes some external
        functions that are not usually used for testing purposes."""
        plib = self.config.parameters.plib
        self.func_p = EF(library=plib, function="p")
        self.func_u = EF(library=plib, function="u")
        self.func_s = EF(library=plib, function="s")
        self.func_h = EF(library=plib, function="h")
        self.func_hvpt = EF(library=plib, function="hvpt")
        self.func_hlpt = EF(library=plib, function="hlpt")
        self.func_tau = EF(library=plib, function="tau")
        self.func_vf = EF(library=plib, function="vf")
        self.func_g = EF(library=plib, function="g")
        self.func_f = EF(library=plib, function="f")
        self.func_cv = EF(library=plib, function="cv")
        self.func_cp = EF(library=plib, function="cp")
        self.func_w = EF(library=plib, function="w")
        self.func_delta_liq = EF(library=plib, function="delta_liq")
        self.func_delta_vap = EF(library=plib, function="delta_vap")
        self.func_delta_sat_l = EF(library=plib, function="delta_sat_l")
        self.func_delta_sat_v = EF(library=plib, function="delta_sat_v")
        self.func_p_sat = EF(library=plib, function="p_sat")
        self.func_tau_sat = EF(library=plib, function="tau_sat")
        self.func_phi0 = EF(library=plib, function="phi0")
        self.func_phi0_delta = EF(library=plib, function="phi0_delta")
        self.func_phi0_delta2 = EF(library=plib, function="phi0_delta2")
        self.func_phi0_tau = EF(library=plib, function="phi0_tau")
        self.func_phi0_tau2 = EF(library=plib, function="phi0_tau2")
        self.func_phir = EF(library=plib, function="phir")
        self.func_phir_delta = EF(library=plib, function="phir_delta")
        self.func_phir_delta2 = EF(library=plib, function="phir_delta2")
        self.func_phir_tau = EF(library=plib, function="phir_tau")
        self.func_phir_tau2 = EF(library=plib, function="phir_tau2")
        self.func_phir_delta_tau = EF(library=plib, function="phir_delta_tau")

    def _state_vars(self):
        """ Create the state variables
        """
        self.flow_mol = Var(
            initialize=1, domain=NonNegativeReals, doc="Total flow [mol/s]"
        )
        self.scaling_factor[self.flow_mol] = 1e-3

        if self.state_vars == StateVars.PH:
            self.pressure = Var(
                domain=PositiveReals,
                initialize=1e5,
                doc="Pressure [Pa]",
                bounds=(1, 1e9),
            )
            self.enth_mol = Var(
                initialize=1000, doc="Total molar enthalpy (J/mol)", bounds=(1, 1e5)
            )
            self.scaling_factor[self.enth_mol] = 1e-3

            P = self.pressure / 1000.0  # Pressure expr [kPA] (for external func)
            h_mass = self.enth_mol / self.mw / 1000  # enthalpy expr [kJ/kg]
            phase_set = self.config.parameters.config.phase_presentation

            self.temperature = Expression(
                expr=self.temperature_crit / self.func_tau(h_mass, P),
                doc="Temperature (K)",
            )
            if phase_set == PhaseType.MIX or phase_set == PhaseType.LG:
                self.vapor_frac = Expression(
                    expr=self.func_vf(h_mass, P),
                    doc="Vapor mole fraction (mol vapor/mol total)",
                )
            elif phase_set == PhaseType.L:
                self.vapor_frac = Expression(
                    expr=0.0, doc="Vapor mole fraction (mol vapor/mol total)"
                )
            elif phase_set == PhaseType.G:
                self.vapor_frac = Expression(
                    expr=1.0, doc="Vapor mole fraction (mol vapor/mol total)"
                )

            # For variables that show up in ports specify extensive/intensive
            self.extensive_set = ComponentSet((self.flow_mol,))
            self.intensive_set = ComponentSet((self.enth_mol, self.pressure))

        elif self.state_vars == StateVars.TPX:
            self.temperature = Var(
                initialize=300, doc="Temperature [K]", bounds=(200, 3e3)
            )

            self.pressure = Var(
                domain=PositiveReals,
                initialize=1e5,
                doc="Pressure [Pa]",
                bounds=(1, 1e9),
            )

            self.vapor_frac = Var(initialize=0.0, doc="Vapor fraction [none]")

            # enth_mol is defined later, since in this case it needs
            # enth_mol_phase to be defined first

            # For variables that show up in ports specify extensive/intensive
            self.extensive_set = ComponentSet((self.flow_mol,))
            self.intensive_set = ComponentSet(
                (self.temperature, self.pressure, self.vapor_frac)
            )
        self.scaling_factor[self.temperature] = 1e-1
        self.scaling_factor[self.pressure] = 1e-6
        self.scaling_factor[self.vapor_frac] = 1e1

    def _tpx_phase_eq(self):
        # Saturation pressure
        eps_pu = self.config.parameters.smoothing_pressure_under
        eps_po = self.config.parameters.smoothing_pressure_over
        priv_plist = self.config.parameters.private_phase_list
        plist = self.config.parameters.phase_list
        rhoc = self.config.parameters.dens_mass_crit

        P = self.pressure / 1000  # expression for pressure in kPa
        Psat = self.pressure_sat / 1000.0  # expression for Psat in kPA
        vf = self.vapor_frac
        tau = self.tau

        # Terms for determining if you are above, below, or at the Psat
        self.P_under_sat = Expression(
            expr=smooth_max(0, Psat - P, eps_pu),
            doc="pressure above Psat, 0 if liqid exists [kPa]",
        )
        self.P_over_sat = Expression(
            expr=smooth_max(0, P - Psat, eps_po),
            doc="pressure below Psat, 0 if vapor exists [kPa]",
        )

        # Calculate liquid and vapor density.  If the phase doesn't exist,
        # density will be calculated at the saturation or critical pressure
        def rule_dens_mass(b, p):
            if p == "Liq":
                self.scaling_factor[self.dens_mass_phase[p]] = 1e-2
                return rhoc * self.func_delta_liq(P + self.P_under_sat, tau)
            else:
                self.scaling_factor[self.dens_mass_phase[p]] = 1e1
                return rhoc * self.func_delta_vap(P - self.P_over_sat, tau)

        self.dens_mass_phase = Expression(priv_plist, rule=rule_dens_mass)

        # Reduced Density (no _mass_ identifier because mass or mol is same)
        def rule_dens_red(b, p):
            return self.dens_mass_phase[p] / rhoc

        self.dens_phase_red = Expression(
            priv_plist, rule=rule_dens_red, doc="reduced density [unitless]"
        )

        # If there is only one phase fix the vapor fraction appropriately
        if len(plist) == 1:
            if "Vap" in plist:
                self.vapor_frac.fix(1.0)
            else:
                self.vapor_frac.fix(0.0)
        elif not self.config.defined_state:
            self.eq_complementarity = Constraint(
                expr=0 == (vf * self.P_over_sat - (1 - vf) * self.P_under_sat)
            )
            self.scaling_expression[self.eq_complementarity] = 10 / self.pressure

        # eq_sat can activated to force the pressure to be the saturation
        # pressure, if you use this constraint deactivate eq_complementarity
        self.eq_sat = Constraint(expr=P / 1000.0 == Psat / 1000.0)
        self.scaling_expression[self.eq_sat] = 1000 / self.pressure
        self.eq_sat.deactivate()


    def build(self, *args):
        """
        Callable method for Block construction
        """
        super().build(*args)

        # Create the scaling suffixes for the state block
        self.scaling_factor = Suffix(direction=Suffix.EXPORT)
        self.scaling_expression = Suffix()

        # Check if the library is available.
        self.available = self.config.parameters.available
        if not self.available:
            _log.error("Library file '{}' not found. Was it installed?".format(
                    self.config.parameter.plib
                )
            )

        # Add external functions
        self._external_functions()

        # Which state vars to use
        self.state_vars = self.config.parameters.state_vars
        # The private phase list contains phases that may be present and is
        # used internally.  If using the single mixed phase option the phase
        # list would be mixed while the private phase list would be ["Liq, "Vap"]
        phlist = self.config.parameters.private_phase_list
        pub_phlist = self.config.parameters.phase_list
        component_list = self.config.parameters.component_list
        phase_set = self.config.parameters.config.phase_presentation
        self.phase_equilibrium_list = self.config.parameters.phase_equilibrium_list

        # Expressions that link to some parameters in the param block, which
        # are commonly needed, this lets you get the parameters with scale
        # factors directly from the state block
        self.temperature_crit = Expression(expr=self.config.parameters.temperature_crit)
        self.scaling_factor[self.temperature_crit] = 1e-2
        self.pressure_crit = Expression(expr=self.config.parameters.pressure_crit)
        self.scaling_factor[self.pressure_crit] = 1e-6
        self.dens_mass_crit = Expression(expr=self.config.parameters.dens_mass_crit)
        self.scaling_factor[self.dens_mass_crit] = 1e-2
        self.gas_const = Expression(expr=self.config.parameters.gas_const)
        self.scaling_factor[self.gas_const] = 1e0
        self.mw = Expression(
            expr=self.config.parameters.mw, doc="molecular weight [kg/mol]"
        )
        self.scaling_factor[self.mw] = 1e3

        # create the appropriate state variables
        self._state_vars()

        # Some parameters/variables show up in several expressions, so to enhance
        # readability and compactness, give them short aliases
        Tc = self.config.parameters.temperature_crit
        rhoc = self.config.parameters.dens_mass_crit
        mw = self.mw
        P = self.pressure / 1000.0  # Pressure expr [kPA] (for external func)
        T = self.temperature
        vf = self.vapor_frac

        # Saturation temperature expression
        self.temperature_sat = Expression(
            expr=Tc / self.func_tau_sat(P), doc="Stauration temperature (K)"
        )
        self.scaling_factor[self.temperature_sat] = 1e-2

        # Saturation tau (tau = Tc/T)
        self.tau_sat = Expression(expr=self.func_tau_sat(P))

        # Reduced temperature
        self.temperature_red = Expression(
            expr=T / Tc, doc="reduced temperature T/Tc (unitless)"
        )
        self.scaling_factor[self.temperature_red] = 1

        self.tau = Expression(expr=Tc / T, doc="Tc/T (unitless)")
        tau = self.tau

        # Saturation pressure
        self.pressure_sat = Expression(
            expr=1000 * self.func_p_sat(tau), doc="Saturation pressure (Pa)"
        )
        self.scaling_factor[self.pressure_sat] = 1e-5

        if self.state_vars == StateVars.PH:
            # If TPx state vars the expressions are given in _tpx_phase_eq
            # Calculate liquid and vapor density.  If the phase doesn't exist,
            # density will be calculated at the saturation or critical pressure
            # depending on whether the temperature is above the critical
            # temperature supercritical fluid is considered to be the liquid
            # phase
            def rule_dens_mass(b, p):
                if p == "Liq":
                    self.scaling_factor[self.dens_mass_phase[p]] = 1e-2
                    return rhoc * self.func_delta_liq(P, tau)
                else:
                    self.scaling_factor[self.dens_mass_phase[p]] = 1e1
                    return rhoc * self.func_delta_vap(P, tau)

            self.dens_mass_phase = Expression(
                phlist, rule=rule_dens_mass, doc="Mass density by phase (kg/m3)"
            )

            # Reduced Density (no _mass_ identifier as mass or mol is same)
            def rule_dens_red(b, p):
                self.scaling_factor[self.dens_phase_red[p]] = 1
                return self.dens_mass_phase[p] / rhoc

            self.dens_phase_red = Expression(
                phlist, rule=rule_dens_red, doc="reduced density (unitless)"
            )

        elif self.state_vars == StateVars.TPX:
            self._tpx_phase_eq()
        delta = self.dens_phase_red

        # Phase property expressions all converted to SI

        # Saturated Enthalpy
        def rule_enth_mol_sat_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.enth_mol_sat_phase[p]] = 1e-2
                return 1000 * mw * self.func_hlpt(P, self.tau_sat)
            elif p == "Vap":
                self.scaling_factor[self.enth_mol_sat_phase[p]] = 1e-4
                return 1000 * mw * self.func_hvpt(P, self.tau_sat)

        self.enth_mol_sat_phase = Expression(
            phlist,
            rule=rule_enth_mol_sat_phase,
            doc="Saturated enthalpy of the phases at pressure (J/mol)",
        )

        self.dh_vap_mol = Expression(
            expr=self.enth_mol_sat_phase["Vap"] - self.enth_mol_sat_phase["Liq"],
            doc="Enthaply of vaporization at pressure and saturation (J/mol)",
        )
        self.scaling_factor[self.dh_vap_mol] = 1e-4

        # Phase Internal Energy
        def rule_energy_internal_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.energy_internal_mol_phase[p]] = 1e-2
            else:
                self.scaling_factor[self.energy_internal_mol_phase[p]] = 1e-4
            return 1000 * mw * self.func_u(delta[p], tau)

        self.energy_internal_mol_phase = Expression(
            phlist,
            rule=rule_energy_internal_mol_phase,
            doc="Phase internal energy or saturated if phase doesn't exist [J/mol]",
        )

        # Phase Enthalpy
        def rule_enth_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.enth_mol_phase[p]] = 1e-2
            elif p == "Vap":
                self.scaling_factor[self.enth_mol_phase[p]] = 1e-4
            return 1000 * mw * self.func_h(delta[p], tau)

        self.enth_mol_phase = Expression(
            phlist,
            rule=rule_enth_mol_phase,
            doc="Phase enthalpy or saturated if phase doesn't exist [J/mol]",
        )

        # Phase Entropy
        def rule_entr_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.entr_mol_phase[p]] = 1e-1
            elif p == "Vap":
                self.scaling_factor[self.entr_mol_phase[p]] = 1e-1
            return 1000 * mw * self.func_s(delta[p], tau)

        self.entr_mol_phase = Expression(
            phlist,
            rule=rule_entr_mol_phase,
            doc="Phase entropy or saturated if phase doesn't exist [J/mol/K]",
        )

        # Phase constant pressure heat capacity, cp
        def rule_cp_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.cp_mol_phase[p]] = 1e-2
            elif p == "Vap":
                self.scaling_factor[self.cp_mol_phase[p]] = 1e-2
            return 1000 * mw * self.func_cp(delta[p], tau)

        self.cp_mol_phase = Expression(
            phlist,
            rule=rule_cp_mol_phase,
            doc="Phase cp or saturated if phase doesn't exist [J/mol/K]",
        )

        # Phase constant pressure heat capacity, cv
        def rule_cv_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.cv_mol_phase[p]] = 1e-2
            elif p == "Vap":
                self.scaling_factor[self.cv_mol_phase[p]] = 1e-2
            return 1000 * mw * self.func_cv(delta[p], tau)

        self.cv_mol_phase = Expression(
            phlist,
            rule=rule_cv_mol_phase,
            doc="Phase cv or saturated if phase doesn't exist [J/mol/K]",
        )

        # Phase speed of sound
        def rule_speed_sound_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.speed_sound_phase[p]] = 1e-2
            elif p == "Vap":
                self.scaling_factor[self.speed_sound_phase[p]] = 1e-2
            return self.func_w(delta[p], tau)

        self.speed_sound_phase = Expression(
            phlist,
            rule=rule_speed_sound_phase,
            doc="Phase speed of sound or saturated if phase doesn't exist [m/s]",
        )

        # Phase Mole density
        def rule_dens_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.dens_mol_phase[p]] = 1e-2
            elif p == "Vap":
                self.scaling_factor[self.dens_mol_phase[p]] = 1e-4
            return self.dens_mass_phase[p] / mw

        self.dens_mol_phase = Expression(
            phlist,
            rule=rule_dens_mol_phase,
            doc="Phase mole density or saturated if phase doesn't exist [mol/m3]",
        )

        # Phase fraction
        def rule_phase_frac(b, p):
            self.scaling_factor[self.phase_frac[p]] = 10
            if p == "Vap":
                return vf
            elif p == "Liq":
                return 1.0 - vf

        self.phase_frac = Expression(
            phlist, rule=rule_phase_frac, doc="Phase fraction [unitless]"
        )

        # Component flow (for units that need it)
        def component_flow(b, i):
            self.scaling_factor[self.flow_mol_comp[i]] = 1e-3
            return self.flow_mol

        self.flow_mol_comp = Expression(
            component_list,
            rule=component_flow,
            doc="Total flow (both phases) of component [mol/s]",
        )

        # Total (mixed phase) properties

        # Enthalpy
        if self.state_vars == StateVars.TPX:
            self.enth_mol = Expression(
                expr=sum(self.phase_frac[p] * self.enth_mol_phase[p] for p in phlist)
            )
            self.scaling_factor[self.enth_mol] = 1e-3
        # Internal Energy
        self.energy_internal_mol = Expression(
            expr=sum(
                self.phase_frac[p] * self.energy_internal_mol_phase[p] for p in phlist
            )
        )
        self.scaling_factor[self.energy_internal_mol] = 1e-3
        # Entropy
        self.entr_mol = Expression(
            expr=sum(self.phase_frac[p] * self.entr_mol_phase[p] for p in phlist)
        )
        self.scaling_factor[self.entr_mol] = 1e-1
        # cp
        self.cp_mol = Expression(
            expr=sum(self.phase_frac[p] * self.cp_mol_phase[p] for p in phlist)
        )
        self.scaling_factor[self.cp_mol] = 1e-2
        # cv
        self.cv_mol = Expression(
            expr=sum(self.phase_frac[p] * self.cv_mol_phase[p] for p in phlist)
        )
        self.scaling_factor[self.cv_mol] = 1e-2
        # mass density
        self.dens_mass = Expression(
            expr=1.0
            / sum(self.phase_frac[p] * 1.0 / self.dens_mass_phase[p] for p in phlist)
        )
        self.scaling_factor[self.dens_mass] = 1e0
        # mole density
        self.dens_mol = Expression(
            expr=1.0
            / sum(self.phase_frac[p] * 1.0 / self.dens_mol_phase[p] for p in phlist)
        )
        self.scaling_factor[self.dens_mol] = 1e-3
        # heat capacity ratio
        self.heat_capacity_ratio = Expression(expr=self.cp_mol / self.cv_mol)
        self.scaling_factor[self.heat_capacity_ratio] = 1e1
        # Flows
        self.flow_vol = Expression(
            expr=self.flow_mol / self.dens_mol,
            doc="Total liquid + vapor volumetric flow (m3/s)",
        )
        self.scaling_factor[self.flow_vol] = 100

        self.flow_mass = Expression(
            expr=self.mw * self.flow_mol, doc="mass flow rate [kg/s]"
        )
        self.scaling_factor[self.flow_mass] = 1

        self.enth_mass = Expression(expr=self.enth_mol / mw, doc="Mass enthalpy (J/kg)")
        self.scaling_factor[self.enth_mass] = 1

        # Set the state vars dictionary
        if self.state_vars == StateVars.PH:
            self._state_vars_dict = {
                "flow_mol": self.flow_mol,
                "enth_mol": self.enth_mol,
                "pressure": self.pressure,
            }
        elif self.state_vars == StateVars.TPX and phase_set in (
            PhaseType.MIX,
            PhaseType.LG,
        ):
            self._state_vars_dict = {
                "flow_mol": self.flow_mol,
                "temperature": self.temperature,
                "pressure": self.pressure,
                "vapor_frac": self.vapor_frac,
            }
        elif self.state_vars == StateVars.TPX and phase_set in (
            PhaseType.G,
            PhaseType.L,
        ):
            self._state_vars_dict = {
                "flow_mol": self.flow_mol,
                "temperature": self.temperature,
                "pressure": self.pressure,
            }

        # Define some expressions for the balance terms returned by functions
        # This is just to allow assigning scale factors to the expressions
        # returned
        #
        # Marterial flow term exprsssions
        def rule_material_flow_terms(b, p):
            self.scaling_expression[b.material_flow_terms[p]] = 1 / self.flow_mol
            if p == "Mix":
                return self.flow_mol
            else:
                return self.flow_mol * self.phase_frac[p]

        self.material_flow_terms = Expression(pub_phlist, rule=rule_material_flow_terms)

        # Enthaply flow term expressions
        def rule_enthalpy_flow_terms(b, p):
            if p == "Mix":
                self.scaling_expression[b.enthalpy_flow_terms[p]] = 1 / (
                    self.enth_mol * self.flow_mol
                )
                return self.enth_mol * self.flow_mol
            else:
                self.scaling_expression[b.enthalpy_flow_terms[p]] = 1 / (
                    self.enth_mol_phase[p] * self.phase_frac[p] * self.flow_mol
                )
                return self.enth_mol_phase[p] * self.phase_frac[p] * self.flow_mol

        self.enthalpy_flow_terms = Expression(pub_phlist, rule=rule_enthalpy_flow_terms)

        # Energy density term expressions
        def rule_energy_density_terms(b, p):
            if p == "Mix":
                self.scaling_expression[b.energy_density_terms[p]] = 1 / (
                    self.energy_internal_mol * self.flow_mol
                )
                return self.dens_mol * self.energy_internal_mol
            else:
                self.scaling_expression[b.energy_density_terms[p]] = 1 / (
                    self.dens_mol_phase[p] * self.energy_internal_mol_phase[p]
                )
                return self.dens_mol_phase[p] * self.energy_internal_mol_phase[p]

        self.energy_density_terms = Expression(
            pub_phlist, rule=rule_energy_density_terms
        )

    def get_material_flow_terms(self, p, j):
        return self.material_flow_terms[p]

    def get_enthalpy_flow_terms(self, p):
        return self.enthalpy_flow_terms[p]

    def get_material_density_terms(self, p, j):
        if p == "Mix":
            return self.dens_mol
        else:
            return self.dens_mol_phase[p]

    def get_energy_density_terms(self, p):
        return self.energy_density_terms[p]

    def default_material_balance_type(self):
        return MaterialBalanceType.componentTotal

    def default_energy_balance_type(self):
        return EnergyBalanceType.enthalpyTotal

    def define_state_vars(self):
        return self._state_vars_dict

    def define_display_vars(self):
        return {
            "Molar Flow (mol/s)": self.flow_mol,
            "Mass Flow (kg/s)": self.flow_mass,
            "T (K)": self.temperature,
            "P (Pa)": self.pressure,
            "Vapor Fraction": self.vapor_frac,
            "Molar Enthalpy (J/mol)": self.enth_mol_phase,
        }

    def extensive_state_vars(self):
        return self.extensive_set

    def intensive_state_vars(self):
        return self.intensive_set

    def model_check(self):
        pass
コード例 #24
0
class TurbineInletStageData(PressureChangerData):
    # Same settings as the default pressure changer, but force to expander with
    # isentropic efficiency
    CONFIG = PressureChangerData.CONFIG()
    CONFIG.compressor = False
    CONFIG.get("compressor")._default = False
    CONFIG.get("compressor")._domain = In([False])
    CONFIG.thermodynamic_assumption = ThermodynamicAssumption.isentropic
    CONFIG.get("thermodynamic_assumption")._default = \
        ThermodynamicAssumption.isentropic
    CONFIG.get("thermodynamic_assumption")._domain = In(
        [ThermodynamicAssumption.isentropic])

    def build(self):
        super(TurbineInletStageData, self).build()

        umeta = self.config.property_package.get_metadata().get_derived_units
        t_units = umeta("temperature")

        self.flow_coeff = Var(
            self.flowsheet().config.time,
            initialize=1.053 / 3600.0,
            doc="Turbine flow coefficient",
            units=(umeta("mass") * umeta("temperature")**0.5 *
                   umeta("pressure")**-1 * umeta("time")**-1))
        self.delta_enth_isentropic = Var(
            self.flowsheet().config.time,
            initialize=-1000,
            doc="Specific enthalpy change of isentropic process",
            units=umeta("energy") / umeta("amount"))
        self.blade_reaction = Var(initialize=0.9,
                                  doc="Blade reaction parameter")
        self.blade_velocity = Var(initialize=110.0,
                                  doc="Design blade velocity",
                                  units=umeta("length") / umeta("time"))
        self.eff_nozzle = Var(
            initialize=0.95,
            bounds=(0.0, 1.0),
            doc="Nozzel efficiency (typically 0.90 to 0.95)",
        )
        self.efficiency_mech = Var(initialize=0.98,
                                   doc="Turbine mechanical efficiency")
        self.flow_scale = Param(
            mutable=True,
            default=1e3,
            doc="Scaling factor for pressure flow relation should be "
            "approximately the same order of magnitude as the expected flow.",
        )
        self.eff_nozzle.fix()
        self.blade_reaction.fix()
        self.flow_coeff.fix()
        self.blade_velocity.fix()
        self.efficiency_mech.fix()
        self.ratioP[:] = 1  # make sure these have a number value
        self.deltaP[:] = 0  # to avoid an error later in initialize

        @self.Expression(
            self.flowsheet().config.time,
            doc="Entering steam velocity calculation",
        )
        def steam_entering_velocity(b, t):
            # 1.414 = 44.72/sqrt(1000) for SI if comparing to Liese (2014)
            # b.delta_enth_isentropic[t] = -(hin - hiesn), the mw converts
            # enthalpy to a mass basis
            return 1.414 * sqrt(
                -(1 - b.blade_reaction) * b.delta_enth_isentropic[t] /
                b.control_volume.properties_in[t].mw * self.eff_nozzle)

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: Turbine inlet flow")
        def inlet_flow_constraint(b, t):
            # Some local vars to make the equation more readable
            g = b.control_volume.properties_in[t].heat_capacity_ratio
            mw = b.control_volume.properties_in[t].mw
            flow = b.control_volume.properties_in[t].flow_mol
            Tin = b.control_volume.properties_in[t].temperature
            cf = b.flow_coeff[t]
            Pin = b.control_volume.properties_in[t].pressure
            Pratio = b.ratioP[t]
            return ((1 / b.flow_scale**2) * flow**2 * mw**2 *
                    (Tin - 273.15 * t_units) == (1 / b.flow_scale**2) * cf**2 *
                    Pin**2 * (g / (g - 1) *
                              (Pratio**(2.0 / g) - Pratio**((g + 1) / g))))

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: Isentropic enthalpy change")
        def isentropic_enthalpy(b, t):
            return b.work_isentropic[t] == (
                b.delta_enth_isentropic[t] *
                b.control_volume.properties_in[t].flow_mol)

        @self.Constraint(self.flowsheet().config.time,
                         doc="Equation: Efficiency")
        def efficiency_correlation(b, t):
            Vr = b.blade_velocity / b.steam_entering_velocity[t]
            eff = b.efficiency_isentropic[t]
            R = b.blade_reaction
            return eff == 2 * Vr * (
                (sqrt(1 - R) - Vr) + sqrt((sqrt(1 - R) - Vr)**2 + R))

        @self.Expression(self.flowsheet().config.time,
                         doc="Thermodynamic power")
        def power_thermo(b, t):
            return b.control_volume.work[t]

        @self.Expression(self.flowsheet().config.time, doc="Shaft power")
        def power_shaft(b, t):
            return b.power_thermo[t] * b.efficiency_mech

    def _get_performance_contents(self, time_point=0):
        pc = super()._get_performance_contents(time_point=time_point)
        pc["vars"]["Mechanical Efficiency"] = self.efficiency_mech
        pc["vars"]["Flow Coefficient"] = self.flow_coeff[time_point]
        pc["vars"]["Isentropic Specific Enthalpy"] = \
            self.delta_enth_isentropic[time_point]
        pc["vars"]["Blade Reaction"] = self.blade_reaction
        pc["vars"]["Blade Velocity"] = self.blade_velocity
        pc["vars"]["Nozzel Efficiency"] = self.eff_nozzle

        pc["exprs"] = {}
        pc["exprs"]["Thermodynamic Power"] = self.power_thermo[time_point]
        pc["exprs"]["Shaft Power"] = self.power_shaft[time_point]
        pc["exprs"]["Inlet Steam Velocity"] = \
            self.steam_entering_velocity[time_point]

        pc["params"] = {}
        pc["params"]["Flow Scaling"] = self.flow_scale

        return pc

    def initialize(
        self,
        state_args={},
        outlvl=idaeslog.NOTSET,
        solver="ipopt",
        optarg={
            "tol": 1e-6,
            "max_iter": 30
        },
    ):
        """
        Initialize the inlet turbine stage model.  This deactivates the
        specialized constraints, then does the isentropic turbine
        initialization, then reactivates the constraints and solves.

        Args:
            state_args (dict): Initial state for property initialization
            outlvl (int): Amount of output (0 to 3) 0 is lowest
            solver (str): Solver to use for initialization
            optarg (dict): Solver arguments dictionary
        """
        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")

        # sp is what to save to make sure state after init is same as the start
        #   saves value, fixed, and active state, doesn't load originally free
        #   values, this makes sure original problem spec is same but
        #   initializes the values of free vars
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)
        # Deactivate special constraints
        self.inlet_flow_constraint.deactivate()
        self.isentropic_enthalpy.deactivate()
        self.efficiency_correlation.deactivate()
        self.deltaP.unfix()
        self.ratioP.unfix()

        # Fix turbine parameters + eff_isen
        self.eff_nozzle.fix()
        self.blade_reaction.fix()
        self.flow_coeff.fix()
        self.blade_velocity.fix()

        # fix inlet and free outlet
        for t in self.flowsheet().config.time:
            for k, v in self.inlet.vars.items():
                v[t].fix()
            for k, v in self.outlet.vars.items():
                v[t].unfix()
            # If there isn't a good guess for efficeny or outlet pressure
            # provide something reasonable.
            eff = self.efficiency_isentropic[t]
            eff.fix(
                eff.value if value(eff) > 0.3 and value(eff) < 1.0 else 0.8)
            # for outlet pressure try outlet pressure, pressure ratio, delta P,
            # then if none of those look reasonable use a pressure ratio of 0.8
            # to calculate outlet pressure
            Pout = self.outlet.pressure[t]
            Pin = self.inlet.pressure[t]
            prdp = value((self.deltaP[t] - Pin) / Pin)
            if value(Pout / Pin) > 0.98 or value(Pout / Pin) < 0.3:
                if (value(self.ratioP[t]) < 0.98
                        and value(self.ratioP[t]) > 0.3):
                    Pout.fix(value(Pin * self.ratioP))
                elif prdp < 0.98 and prdp > 0.3:
                    Pout.fix(value(prdp * Pin))
                else:
                    Pout.fix(value(Pin * 0.8))
            else:
                Pout.fix()
        self.deltaP[:] = value(Pout - Pin)
        self.ratioP[:] = value(Pout / Pin)

        for t in self.flowsheet().config.time:
            self.properties_isentropic[t].pressure.value = value(
                self.outlet.pressure[t])
            self.properties_isentropic[t].flow_mol.value = value(
                self.inlet.flow_mol[t])
            self.properties_isentropic[t].enth_mol.value = value(
                self.inlet.enth_mol[t] * 0.95)
            self.outlet.flow_mol[t].value = value(self.inlet.flow_mol[t])
            self.outlet.enth_mol[t].value = value(self.inlet.enth_mol[t] *
                                                  0.95)

        # Make sure the initialization problem has no degrees of freedom
        # This shouldn't happen here unless there is a bug in this
        dof = degrees_of_freedom(self)
        try:
            assert dof == 0
        except:
            init_log.exception("degrees_of_freedom = {}".format(dof))
            raise

        # one bad thing about reusing this is that the log messages aren't
        # really compatible with being nested inside another initialization
        super().initialize(state_args=state_args,
                           outlvl=outlvl,
                           solver=solver,
                           optarg=optarg)

        # Free eff_isen and activate sepcial constarints
        self.efficiency_isentropic.unfix()
        self.outlet.pressure.unfix()
        self.inlet_flow_constraint.activate()
        self.isentropic_enthalpy.activate()
        self.efficiency_correlation.activate()

        slvr = SolverFactory(solver)
        slvr.options = optarg
        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = slvr.solve(self, tee=slc.tee)

        init_log.info("Initialization Complete: {}".format(
            idaeslog.condition(res)))

        # reload original spec
        from_json(self, sd=istate, wts=sp)
コード例 #25
0
ファイル: turbine_inlet.py プロジェクト: jkreuder/idaes-pse
class HelmTurbineInletStageData(HelmIsentropicTurbineData):
    CONFIG = HelmIsentropicTurbineData.CONFIG()

    def build(self):
        super().build()

        self.flow_coeff = Var(
            self.flowsheet().config.time,
            initialize=1.053 / 3600.0,
            doc="Turbine flow coefficient [kg*C^0.5/Pa/s]",
        )
        self.blade_reaction = Var(
            initialize=0.9,
            doc="Blade reaction parameter"
        )
        self.blade_velocity = Var(
            initialize=110.0,
            doc="Design blade velocity [m/s]"
        )
        self.eff_nozzle = Var(
            initialize=0.95,
            bounds=(0.0, 1.0),
            doc="Nozzel efficiency (typically 0.90 to 0.95)",
        )
        self.efficiency_mech = Var(
            initialize=1.0,
            doc="Turbine mechanical efficiency"
        )

        self.eff_nozzle.fix()
        self.blade_reaction.fix()
        self.flow_coeff.fix()
        self.blade_velocity.fix()
        self.efficiency_mech.fix()
        self.efficiency_isentropic.unfix()
        self.ratioP[:] = 0.9  # make sure these have a number value
        self.deltaP[:] = 0  #   to avoid an error later in initialize

        @self.Expression(
            self.flowsheet().config.time,
            doc="Entering steam velocity calculation [m/s]",
        )
        def steam_entering_velocity(b, t):
            # 1.414 = 44.72/sqrt(1000) for SI if comparing to Liese (2014),
            # b.delta_enth_isentropic[t] = -(hin - hiesn), the mw converts
            # enthalpy to a mass basis
            return 1.414 * sqrt(
                (b.blade_reaction - 1)*b.delta_enth_isentropic[t]*self.eff_nozzle
                / b.control_volume.properties_in[t].mw
            )

        @self.Expression(self.flowsheet().config.time, doc="Efficiency expression")
        def efficiency_isentropic_expr(b, t):
            Vr = b.blade_velocity / b.steam_entering_velocity[t]
            R = b.blade_reaction
            return 2*Vr*((sqrt(1 - R) - Vr) + sqrt((sqrt(1 - R) - Vr)**2 + R))

        @self.Constraint(
            self.flowsheet().config.time, doc="Equation: Turbine inlet flow")
        def inlet_flow_constraint(b, t):
            # Some local vars to make the equation more readable
            g = b.control_volume.properties_in[t].heat_capacity_ratio
            mw = b.control_volume.properties_in[t].mw
            flow = b.control_volume.properties_in[t].flow_mol
            Tin = b.control_volume.properties_in[t].temperature
            cf = b.flow_coeff[t]
            Pin = b.control_volume.properties_in[t].pressure
            Pratio = b.ratioP[t]
            return flow ** 2 * mw ** 2 * Tin == (
                cf ** 2 * Pin ** 2 * g / (g - 1)
                    * (Pratio ** (2.0 / g) - Pratio ** ((g + 1) / g)))

        @self.Constraint(self.flowsheet().config.time, doc="Equation: Efficiency")
        def efficiency_correlation(b, t):
            return b.efficiency_isentropic[t] == b.efficiency_isentropic_expr[t]

        @self.Expression(self.flowsheet().config.time, doc="Thermodynamic power [J/s]")
        def power_thermo(b, t):
            return b.control_volume.work[t]

        @self.Expression(self.flowsheet().config.time, doc="Shaft power [J/s]")
        def power_shaft(b, t):
            return b.power_thermo[t] * b.efficiency_mech


    def initialize(
        self,
        state_args={},
        outlvl=idaeslog.NOTSET,
        solver="ipopt",
        optarg={"tol": 1e-6, "max_iter": 30},
        calculate_cf=False,
    ):
        """
        Initialize the inlet turbine stage model.  This deactivates the
        specialized constraints, then does the isentropic turbine initialization,
        then reactivates the constraints and solves. This initializtion uses a
        flow value guess, so some reasonable flow guess should be sepecified prior
        to initializtion.

        Args:
            state_args (dict): Initial state for property initialization
            outlvl (int): Amount of output (0 to 3) 0 is lowest
            solver (str): Solver to use for initialization
            optarg (dict): Solver arguments dictionary
            calculate_cf (bool): If True, use the flow and pressure ratio to
                calculate the flow coefficient.
        """
        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")

        # sp is what to save to make sure state after init is same as the start
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)

        # Setup for initializtion step 1
        self.inlet_flow_constraint.deactivate()
        self.efficiency_correlation.deactivate()
        self.eff_nozzle.fix()
        self.blade_reaction.fix()
        self.flow_coeff.fix()
        self.blade_velocity.fix()
        self.inlet.fix()
        self.outlet.unfix()

        for t in self.flowsheet().config.time:
            self.efficiency_isentropic[t] = 0.9
        super().initialize(outlvl=outlvl, solver=solver, optarg=optarg)

        # Free eff_isen and activate sepcial constarints
        self.inlet_flow_constraint.activate()
        self.efficiency_correlation.activate()

        if calculate_cf:
            self.ratioP.fix()
            self.flow_coeff.unfix()

            for t in self.flowsheet().config.time:
                g = self.control_volume.properties_in[t].heat_capacity_ratio
                mw = self.control_volume.properties_in[t].mw
                flow = self.control_volume.properties_in[t].flow_mol
                Tin = self.control_volume.properties_in[t].temperature
                Pin = self.control_volume.properties_in[t].pressure
                Pratio = self.ratioP[t]
                self.flow_coeff[t].value = value(
                    flow * mw * sqrt(
                        Tin/(g/(g - 1) *(Pratio**(2.0/g) - Pratio**((g + 1)/g)))
                    )/Pin
                )

        slvr = SolverFactory(solver)
        slvr.options = optarg
        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = slvr.solve(self, tee=slc.tee)
        init_log.info("Initialization Complete: {}".format(idaeslog.condition(res)))
        # reload original spec
        if calculate_cf:
            cf = {}
            for t in self.flowsheet().config.time:
                cf[t] = value(self.flow_coeff[t])

        from_json(self, sd=istate, wts=sp)
        if calculate_cf:
            # cf was probably fixed, so will have to set the value agian here
            # if you ask for it to be calculated.
            for t in self.flowsheet().config.time:
                self.flow_coeff[t] = cf[t]

    def calculate_scaling_factors(self):
        super().calculate_scaling_factors()
        for t, c in self.inlet_flow_constraint.items():
            s = iscale.get_scaling_factor(
                self.control_volume.properties_in[t].flow_mol)**2
            iscale.constraint_scaling_transform(c, s)
コード例 #26
0
class CoagulationFlocculationData(UnitModelBlockData):
    """
    Zero order Coagulation-Flocculation model based on Jar Tests
    """
    # CONFIG are options for the unit model
    CONFIG = ConfigBlock()

    CONFIG.declare("dynamic", ConfigValue(
        domain=In([False]),
        default=False,
        description="Dynamic model flag - must be False",
        doc="""Indicates whether this model will be dynamic or not,
    **default** = False. The filtration unit does not support dynamic
    behavior, thus this must be False."""))

    CONFIG.declare("has_holdup", ConfigValue(
        default=False,
        domain=In([False]),
        description="Holdup construction flag - must be False",
        doc="""Indicates whether holdup terms should be constructed or not.
    **default** - False. The filtration unit does not have defined volume, thus
    this must be False."""))

    CONFIG.declare("material_balance_type", ConfigValue(
        default=MaterialBalanceType.useDefault,
        domain=In(MaterialBalanceType),
        description="Material balance construction flag",
        doc="""Indicates what type of mass balance should be constructed,
    **default** - MaterialBalanceType.useDefault.
    **Valid values:** {
    **MaterialBalanceType.useDefault - refer to property package for default
    balance type
    **MaterialBalanceType.none** - exclude material balances,
    **MaterialBalanceType.componentPhase** - use phase component balances,
    **MaterialBalanceType.componentTotal** - use total component balances,
    **MaterialBalanceType.elementTotal** - use total element balances,
    **MaterialBalanceType.total** - use total material balance.}"""))

    # NOTE: This option is temporarily disabled
    '''
    CONFIG.declare("energy_balance_type", ConfigValue(
        default=EnergyBalanceType.useDefault,
        domain=In(EnergyBalanceType),
        description="Energy balance construction flag",
        doc="""Indicates what type of energy balance should be constructed,
    **default** - EnergyBalanceType.useDefault.
    **Valid values:** {
    **EnergyBalanceType.useDefault - refer to property package for default
    balance type
    **EnergyBalanceType.none** - exclude energy balances,
    **EnergyBalanceType.enthalpyTotal** - single enthalpy balance for material,
    **EnergyBalanceType.enthalpyPhase** - enthalpy balances for each phase,
    **EnergyBalanceType.energyTotal** - single energy balance for material,
    **EnergyBalanceType.energyPhase** - energy balances for each phase.}"""))
    '''

    CONFIG.declare("momentum_balance_type", ConfigValue(
        default=MomentumBalanceType.pressureTotal,
        domain=In(MomentumBalanceType),
        description="Momentum balance construction flag",
        doc="""Indicates what type of momentum balance should be constructed,
    **default** - MomentumBalanceType.pressureTotal.
    **Valid values:** {
    **MomentumBalanceType.none** - exclude momentum balances,
    **MomentumBalanceType.pressureTotal** - single pressure balance for material,
    **MomentumBalanceType.pressurePhase** - pressure balances for each phase,
    **MomentumBalanceType.momentumTotal** - single momentum balance for material,
    **MomentumBalanceType.momentumPhase** - momentum balances for each phase.}"""))

    CONFIG.declare("property_package", ConfigValue(
        default=useDefault,
        domain=is_physical_parameter_block,
        description="Property package to use for control volume",
        doc="""Property parameter object used to define property calculations,
    **default** - useDefault.
    **Valid values:** {
    **useDefault** - use default package from parent model or flowsheet,
    **PhysicalParameterObject** - a PhysicalParameterBlock object.}"""))

    CONFIG.declare("property_package_args", ConfigBlock(
        implicit=True,
        description="Arguments to use for constructing property packages",
        doc="""A ConfigBlock with arguments to be passed to a property block(s)
    and used when constructing these,
    **default** - None.
    **Valid values:** {
    see property package for documentation.}"""))

    CONFIG.declare("chemical_additives", ConfigValue(
        default={},
        domain=dict,
        description="""Dictionary of chemical additives used in coagulation process,
        along with their molecular weights, the moles of salt produced per mole of
        chemical added, and the molecular weights of the salt produced by the chemical
        additive with the format of: \n
            {'chem_name_1':
                {'parameter_data':
                    {
                    'mw_additive': (value, units),
                    'moles_salt_per_mole_additive': value,
                    'mw_salt': (value, units)
                    }
                },
            'chem_name_2':
                {'parameter_data':
                    {
                    'mw_additive': (value, units),
                    'moles_salt_per_mole_additive': value,
                    'mw_salt': (value, units)
                    }
                },
            } """))


    def build(self):
        # build always starts by calling super().build()
        # This triggers a lot of boilerplate in the background for you
        super().build()

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

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

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

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

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

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

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

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

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

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

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

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


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

        self.control_volume.add_state_blocks(
            has_phase_equilibrium=False)

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

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

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

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

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

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

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

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

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

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

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

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

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

    # Return a scalar expression for the inlet concentration of TSS
    def compute_inlet_tss_mass_concentration(self, t):
        """
        Function to generate an expression that would represent the mass
        concentration of TSS at the inlet port of the unit. Inlet ports
        are generally established upstream, but this will be useful for
        establishing the inlet TSS when an upstream TSS is unknown. This
        level of inlet TSS is based off of measurements made of Turbidity
        during the Jar Test.

        Keyword Arguments:
            self : this unit model object
            t : time index on the flowsheet

        Returns: Expression

        Recover the numeric value by using 'value(Expression)'
        """
        units_meta = self.config.property_package.get_metadata().get_derived_units
        return pyunits.convert(self.slope[t]*self.initial_turbidity_ntu[t] + self.intercept[t],
                                to_units=units_meta('mass')*units_meta('length')**-3)

    # Return a scale expression for the inlet mass flow rate of TSS
    def compute_inlet_tss_mass_flow(self, t):
        """
        Function to generate an expression that would represent the mass
        flow rate of TSS at the inlet port of the unit. Inlet ports
        are generally established upstream, but this will be useful for
        establishing the inlet TSS when an upstream TSS is unknown. This
        level of inlet TSS is based off of measurements made of Turbidity
        during the Jar Test.

        Keyword Arguments:
            self : this unit model object
            t : time index on the flowsheet

        Returns: Expression

        Recover the numeric value by using 'value(Expression)'
        """
        return self.control_volume.properties_in[t].flow_vol_phase['Liq']*self.compute_inlet_tss_mass_concentration(t)

    # Function to automate fixing of the Turbidity v TSS relation params to defaults
    def fix_tss_turbidity_relation_defaults(self):
        self.slope.fix()
        self.intercept.fix()

    # initialize method
    def initialize_build(
            blk,
            state_args=None,
            outlvl=idaeslog.NOTSET,
            solver=None,
            optarg=None):
        """
        General wrapper for pressure changer initialization routines

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

        Returns: None
        """
        init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit")
        # Set solver options
        opt = get_solver(solver, optarg)

        # ---------------------------------------------------------------------
        # Initialize holdup block
        flags = blk.control_volume.initialize(
            outlvl=outlvl,
            optarg=optarg,
            solver=solver,
            state_args=state_args,
        )
        init_log.info_high("Initialization Step 1 Complete.")
        # ---------------------------------------------------------------------

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

        # ---------------------------------------------------------------------
        # Release Inlet state
        blk.control_volume.release_state(flags, outlvl + 1)
        init_log.info(
            "Initialization Complete: {}".format(idaeslog.condition(res))
        )

    def calculate_scaling_factors(self):
        super().calculate_scaling_factors()

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

        # scaling factors for turbidity relationship
        #       Supressing warning (these factors are not very important)
        if iscale.get_scaling_factor(self.slope) is None:
            sf = iscale.get_scaling_factor(self.slope, default=1, warning=False)
            iscale.set_scaling_factor(self.slope, sf)
        if iscale.get_scaling_factor(self.intercept) is None:
            sf = iscale.get_scaling_factor(self.intercept, default=1, warning=False)
            iscale.set_scaling_factor(self.intercept, sf)

        # scaling factors for turbidity measurements and chemical doses
        #       Supressing warning
        if iscale.get_scaling_factor(self.initial_turbidity_ntu) is None:
            sf = iscale.get_scaling_factor(self.initial_turbidity_ntu, default=1, warning=False)
            iscale.set_scaling_factor(self.initial_turbidity_ntu, sf)
        if iscale.get_scaling_factor(self.final_turbidity_ntu) is None:
            sf = iscale.get_scaling_factor(self.final_turbidity_ntu, default=1, warning=False)
            iscale.set_scaling_factor(self.final_turbidity_ntu, sf)
        if iscale.get_scaling_factor(self.chemical_doses) is None:
            sf = iscale.get_scaling_factor(self.chemical_doses, default=1, warning=False)
            iscale.set_scaling_factor(self.chemical_doses, sf)

        # set scaling for tss_loss_rate
        if iscale.get_scaling_factor(self.tss_loss_rate) is None:
            sf = 0
            for t in self.control_volume.properties_in:
                sf += value(self.control_volume.properties_in[t].flow_mass_phase_comp['Liq','TSS'])
            sf = sf / len(self.control_volume.properties_in)
            if sf < 0.01:
                sf = 0.01
            iscale.set_scaling_factor(self.tss_loss_rate, 1/sf)

            for ind, c in self.eq_tss_loss_rate.items():
                iscale.constraint_scaling_transform(c, 1/sf)

        # set scaling for tds_gain_rate
        if self.config.chemical_additives:
            if iscale.get_scaling_factor(self.tds_gain_rate) is None:
                sf = 0
                for t in self.control_volume.properties_in:
                    sum = 0
                    for j in self.config.chemical_additives.keys():
                        chem_dose = pyunits.convert(self.chemical_doses[t, j],
                                        to_units=units_meta('mass')*units_meta('length')**-3)
                        chem_dose = chem_dose/self.chemical_mw[j] * \
                                self.salt_from_additive_mole_ratio[j] * \
                                self.salt_mw[j]*self.control_volume.properties_in[t].flow_vol_phase['Liq']
                        sum = sum+chem_dose
                    sf += value(sum)
                sf = sf / len(self.control_volume.properties_in)
                if sf < 0.001:
                    sf = 0.001
                iscale.set_scaling_factor(self.tds_gain_rate, 1/sf)

                for ind, c in self.eq_tds_gain_rate.items():
                    iscale.constraint_scaling_transform(c, 1/sf)

        # set scaling for mass transfer terms
        for ind, c in self.eq_mass_transfer_term.items():
            if ind[2] == "TDS":
                if self.config.chemical_additives:
                    sf = iscale.get_scaling_factor(self.tds_gain_rate)
                else:
                    sf = 1
            elif ind[2] == "TSS":
                sf = iscale.get_scaling_factor(self.tss_loss_rate)
            elif ind[2] == "Sludge":
                sf = iscale.get_scaling_factor(self.tss_loss_rate)
            else:
                sf = 1
            iscale.constraint_scaling_transform(c, sf)
            iscale.set_scaling_factor(self.control_volume.mass_transfer_term[ind] , sf)

        # set scaling factors for control_volume.properties_in based on control_volume.properties_out
        for t in self.control_volume.properties_in:
            if iscale.get_scaling_factor(self.control_volume.properties_in[t].dens_mass_phase) is None:
                sf = iscale.get_scaling_factor(self.control_volume.properties_out[t].dens_mass_phase)
                iscale.set_scaling_factor(self.control_volume.properties_in[t].dens_mass_phase, sf)

            if iscale.get_scaling_factor(self.control_volume.properties_in[t].flow_mass_phase_comp) is None:
                for ind in self.control_volume.properties_in[t].flow_mass_phase_comp:
                    sf = iscale.get_scaling_factor(self.control_volume.properties_out[t].flow_mass_phase_comp[ind])
                    iscale.set_scaling_factor(self.control_volume.properties_in[t].flow_mass_phase_comp[ind], sf)

            if iscale.get_scaling_factor(self.control_volume.properties_in[t].mass_frac_phase_comp) is None:
                for ind in self.control_volume.properties_in[t].mass_frac_phase_comp:
                    sf = iscale.get_scaling_factor(self.control_volume.properties_out[t].mass_frac_phase_comp[ind])
                    iscale.set_scaling_factor(self.control_volume.properties_in[t].mass_frac_phase_comp[ind], sf)

            if iscale.get_scaling_factor(self.control_volume.properties_in[t].flow_vol_phase) is None:
                for ind in self.control_volume.properties_in[t].flow_vol_phase:
                    sf = iscale.get_scaling_factor(self.control_volume.properties_out[t].flow_vol_phase[ind])
                    iscale.set_scaling_factor(self.control_volume.properties_in[t].flow_vol_phase[ind], sf)

        # update scaling for control_volume.properties_out
        for t in self.control_volume.properties_out:
            if iscale.get_scaling_factor(self.control_volume.properties_out[t].dens_mass_phase) is None:
                iscale.set_scaling_factor(self.control_volume.properties_out[t].dens_mass_phase, 1e-3)

            # need to update scaling factors for TSS, Sludge, and TDS to account for the
            #   expected change in their respective values from the loss/gain rates
            for ind in self.control_volume.properties_out[t].flow_mass_phase_comp:
                if ind[1] == "TSS":
                    sf_og = iscale.get_scaling_factor(self.control_volume.properties_out[t].flow_mass_phase_comp[ind])
                    sf_new = iscale.get_scaling_factor(self.tss_loss_rate)
                    iscale.set_scaling_factor(self.control_volume.properties_out[t].flow_mass_phase_comp[ind], 100*sf_new*(sf_new/sf_og))
                if ind[1] == "Sludge":
                    sf_og = iscale.get_scaling_factor(self.control_volume.properties_out[t].flow_mass_phase_comp[ind])
                    sf_new = iscale.get_scaling_factor(self.tss_loss_rate)
                    iscale.set_scaling_factor(self.control_volume.properties_out[t].flow_mass_phase_comp[ind], 100*sf_new*(sf_new/sf_og))

            for ind in self.control_volume.properties_out[t].mass_frac_phase_comp:
                if ind[1] == "TSS":
                    sf_og = iscale.get_scaling_factor(self.control_volume.properties_out[t].mass_frac_phase_comp[ind])
                    sf_new = iscale.get_scaling_factor(self.tss_loss_rate)
                    iscale.set_scaling_factor(self.control_volume.properties_out[t].mass_frac_phase_comp[ind], 100*sf_new*(sf_new/sf_og))
                if ind[1] == "Sludge":
                    sf_og = iscale.get_scaling_factor(self.control_volume.properties_out[t].mass_frac_phase_comp[ind])
                    sf_new = iscale.get_scaling_factor(self.tss_loss_rate)
                    iscale.set_scaling_factor(self.control_volume.properties_out[t].mass_frac_phase_comp[ind], 100*sf_new*(sf_new/sf_og))
コード例 #27
0
class PhysicalParameterData(PhysicalParameterBlock):
    """
    Property Parameter Block Class

    Contains parameters and indexing sets associated with properties for
    methane CLC.
    """
    def build(self):
        '''
        Callable method for Block construction.
        '''
        super(PhysicalParameterData, self).build()

        self._state_block_class = SolidPhaseStateBlock

        # Create Phase object
        self.Sol = SolidPhase()

        # Create Component objects
        self.Fe2O3 = Component()
        self.Fe3O4 = Component()
        self.Al2O3 = Component()

        # -------------------------------------------------------------------------
        """ Pure solid component properties"""

        # Mol. weights of solid components - units = kg/mol. ref: NIST webbook
        mw_comp_dict = {'Fe2O3': 0.15969, 'Fe3O4': 0.231533, 'Al2O3': 0.10196}
        # Molecular weight should be defined in default units
        # (default mass units)/(default amount units)
        # per the define_meta.add_default_units method below
        self.mw_comp = Param(
            self.component_list,
            mutable=False,
            initialize=mw_comp_dict,
            doc="Molecular weights of solid components [kg/mol]",
            units=pyunits.kg / pyunits.mol)

        # Skeletal density of solid components - units = kg/m3. ref: NIST
        dens_mass_comp_skeletal_dict = {
            'Fe2O3': 5250,
            'Fe3O4': 5000,
            'Al2O3': 3987
        }
        self.dens_mass_comp_skeletal = Param(
            self.component_list,
            mutable=False,
            initialize=dens_mass_comp_skeletal_dict,
            doc='Skeletal density of solid components [kg/m3]',
            units=pyunits.kg / pyunits.m**3)

        # Ideal gas spec. heat capacity parameters(Shomate) of
        # components - ref: NIST webbook. Shomate equations from NIST.
        # Parameters A-E are used for cp calcs while A-H are used for enthalpy
        # calc.
        # cp_comp = A + B*T + C*T^2 + D*T^3 + E/(T^2)
        # where T = Temperature (K)/1000, and cp_comp = (J/mol.K)
        # H_comp = H - H(298.15) = A*T + B*T^2/2 + C*T^3/3 +
        # D*T^4/4 - E/T + F - H where T = Temp (K)/1000 and H_comp = (kJ/mol)
        cp_param_dict = {
            ('Al2O3', 1): 102.4290,
            ('Al2O3', 2): 38.74980,
            ('Al2O3', 3): -15.91090,
            ('Al2O3', 4): 2.628181,
            ('Al2O3', 5): -3.007551,
            ('Al2O3', 6): -1717.930,
            ('Al2O3', 7): 146.9970,
            ('Al2O3', 8): -1675.690,
            ('Fe3O4', 1): 200.8320000,
            ('Fe3O4', 2): 1.586435e-7,
            ('Fe3O4', 3): -6.661682e-8,
            ('Fe3O4', 4): 9.452452e-9,
            ('Fe3O4', 5): 3.18602e-8,
            ('Fe3O4', 6): -1174.1350000,
            ('Fe3O4', 7): 388.0790000,
            ('Fe3O4', 8): -1120.8940000,
            ('Fe2O3', 1): 110.9362000,
            ('Fe2O3', 2): 32.0471400,
            ('Fe2O3', 3): -9.1923330,
            ('Fe2O3', 4): 0.9015060,
            ('Fe2O3', 5): 5.4336770,
            ('Fe2O3', 6): -843.1471000,
            ('Fe2O3', 7): 228.3548000,
            ('Fe2O3', 8): -825.5032000
        }
        self.cp_param_1 = Param(
            self.component_list,
            mutable=False,
            initialize={k: v
                        for (k, j), v in cp_param_dict.items() if j == 1},
            doc="Shomate equation heat capacity coeff 1",
            units=pyunits.J / pyunits.mol / pyunits.K)
        self.cp_param_2 = Param(
            self.component_list,
            mutable=False,
            initialize={k: v
                        for (k, j), v in cp_param_dict.items() if j == 2},
            doc="Shomate equation heat capacity coeff 2",
            units=pyunits.J / pyunits.mol / pyunits.K / pyunits.kK)
        self.cp_param_3 = Param(
            self.component_list,
            mutable=False,
            initialize={k: v
                        for (k, j), v in cp_param_dict.items() if j == 3},
            doc="Shomate equation heat capacity coeff 3",
            units=pyunits.J / pyunits.mol / pyunits.K / pyunits.kK**2)
        self.cp_param_4 = Param(
            self.component_list,
            mutable=False,
            initialize={k: v
                        for (k, j), v in cp_param_dict.items() if j == 4},
            doc="Shomate equation heat capacity coeff 4",
            units=pyunits.J / pyunits.mol / pyunits.K / pyunits.kK**3)
        self.cp_param_5 = Param(
            self.component_list,
            mutable=False,
            initialize={k: v
                        for (k, j), v in cp_param_dict.items() if j == 5},
            doc="Shomate equation heat capacity coeff 5",
            units=pyunits.J / pyunits.mol / pyunits.K * pyunits.kK**2)
        self.cp_param_6 = Param(
            self.component_list,
            mutable=False,
            initialize={k: v
                        for (k, j), v in cp_param_dict.items() if j == 6},
            doc="Shomate equation heat capacity coeff 6",
            units=pyunits.kJ / pyunits.mol)
        self.cp_param_7 = Param(
            self.component_list,
            mutable=False,
            initialize={k: v
                        for (k, j), v in cp_param_dict.items() if j == 7},
            doc="Shomate equation heat capacity coeff 7",
            units=pyunits.J / pyunits.mol / pyunits.K)
        self.cp_param_8 = Param(
            self.component_list,
            mutable=False,
            initialize={k: v
                        for (k, j), v in cp_param_dict.items() if j == 8},
            doc="Shomate equation heat capacity coeff 8",
            units=pyunits.kJ / pyunits.mol)

        # Std. heat of formation of comp. - units = J/(mol comp) - ref: NIST
        enth_mol_form_comp_dict = {
            'Fe2O3': -825.5032E3,
            'Fe3O4': -1120.894E3,
            'Al2O3': -1675.690E3
        }
        self.enth_mol_form_comp = Param(
            self.component_list,
            mutable=False,
            initialize=enth_mol_form_comp_dict,
            doc="Component molar heats of formation [J/mol]",
            units=pyunits.J / pyunits.mol)

        # Set default scaling for mass fractions
        for comp in self.component_list:
            self.set_default_scaling("mass_frac_comp", 1e2, index=comp)

    # -------------------------------------------------------------------------
        """ Mixed solid properties"""
        # These are setup as fixed vars to allow for parameter estimation

        # Particle size
        self.particle_dia = Var(domain=Reals,
                                initialize=1.5e-3,
                                doc='Diameter of solid particles [m]',
                                units=pyunits.m)
        self.particle_dia.fix()

        # TODO -provide reference
        # Minimum fluidization velocity - EPAT value used for Davidson model
        self.velocity_mf = Var(domain=Reals,
                               initialize=0.039624,
                               doc='Velocity at minimum fluidization [m/s]',
                               units=pyunits.m / pyunits.s)
        self.velocity_mf.fix()

        # Minimum fluidization voidage - educated guess as rough
        # estimate from ergun equation results (0.4) are suspicious
        self.voidage_mf = Var(domain=Reals,
                              initialize=0.45,
                              doc='Voidage at minimum fluidization [-]',
                              units=pyunits.m**3 / pyunits.m**3)
        self.voidage_mf.fix()

        # Voidage of the bed
        self.voidage = Var(domain=Reals,
                           initialize=0.35,
                           doc='Voidage [-]',
                           units=pyunits.m**3 / pyunits.m**3)
        self.voidage.fix()

        # Particle thermal conductivity
        self.therm_cond_sol = Var(
            domain=Reals,
            initialize=12.3e-0,
            doc='Thermal conductivity of solid particles [J/m.K.s]',
            units=pyunits.J / pyunits.m / pyunits.K / pyunits.s)
        self.therm_cond_sol.fix()

    @classmethod
    def define_metadata(cls, obj):
        obj.add_properties({
            'flow_mass': {
                'method': None,
                'units': 'kg/s'
            },
            'particle_porosity': {
                'method': None,
                'units': None
            },
            'temperature': {
                'method': None,
                'units': 'K'
            },
            'mass_frac_comp': {
                'method': None,
                'units': None
            },
            'dens_mass_skeletal': {
                'method': '_dens_mass_skeletal',
                'units': 'kg/m3'
            },
            'dens_mass_particle': {
                'method': '_dens_mass_particle',
                'units': 'kg/m3'
            },
            'cp_mol_comp': {
                'method': '_cp_mol_comp',
                'units': 'J/mol.K'
            },
            'cp_mass': {
                'method': '_cp_mass',
                'units': 'J/kg.K'
            },
            'enth_mass': {
                'method': '_enth_mass',
                'units': 'J/kg'
            },
            'enth_mol_comp': {
                'method': '_enth_mol_comp',
                'units': 'J/mol'
            }
        })

        obj.add_default_units({
            'time': pyunits.s,
            'length': pyunits.m,
            'mass': pyunits.kg,
            'amount': pyunits.mol,
            'temperature': pyunits.K
        })