Esempio n. 1
0
class HelmIsentropicTurbineData(BalanceBlockData):
    """
    Basic isentropic 0D turbine model.  This inherits the heater block to get
    a lot of unit model boilerplate and the mass balance, enegy balance and
    pressure equations.  This model is intended to be used only with Helmholtz
    EOS property pacakges in mixed or single phase mode with P-H state vars.

    Since this inherits BalanceBlockData, and only operates in steady-state or
    pseudo-steady-state (for dynamic models) the following mass, energy and
    pressure equations are implicitly writen.

    1) Mass Balance:
        0 = flow_mol_in[t] - flow_mol_out[t]
    2) Energy Balance:
        0 = (flow_mol[t]*h_mol[t])_in - (flow_mol[t]*h_mol[t])_out + Q_in + W_in
    3) Pressure:
        0 = P_in[t] + deltaP[t] - P_out[t]
    """

    CONFIG = BalanceBlockData.CONFIG()
    # For dynamics assume pseudo-steady-state
    CONFIG.dynamic = False
    CONFIG.get("dynamic")._default = False
    CONFIG.get("dynamic")._domain = In([False])
    CONFIG.has_holdup = False
    CONFIG.get("has_holdup")._default = False
    CONFIG.get("has_holdup")._domain = In([False])
    # Rest of config to make this function like a turbine
    CONFIG.has_pressure_change = True
    CONFIG.get("has_pressure_change")._default = True
    CONFIG.get("has_pressure_change")._domain = In([True])
    CONFIG.has_work_transfer = True
    CONFIG.get("has_work_transfer")._default = True
    CONFIG.get("has_work_transfer")._domain = In([True])
    CONFIG.has_heat_transfer = False
    CONFIG.get("has_heat_transfer")._default = False
    CONFIG.get("has_heat_transfer")._domain = In([False])

    def build(self):
        """
        Add model equations to the unit model.  This is called by a default block
        construnction rule when the unit model is created.
        """
        super().build()  # Basic unit model build/read config
        config = self.config  # shorter config pointer

        # The thermodynamic expression writer object, te, writes expressions
        # including external function calls to calculate thermodynamic quantities
        # from a set of state variables.
        _assert_properties(config.property_package)
        te = ThermoExpr(blk=self, parameters=config.property_package)

        eff = self.efficiency_isentropic = pyo.Var(
            self.flowsheet().config.time,
            initialize=0.9,
            doc="Isentropic efficiency")
        eff.fix()

        pratio = self.ratioP = pyo.Var(self.flowsheet().config.time,
                                       initialize=0.7,
                                       doc="Ratio of outlet to inlet pressure")

        # Some shorter refernces to property blocks
        properties_in = self.control_volume.properties_in
        properties_out = self.control_volume.properties_out

        @self.Expression(self.flowsheet().config.time,
                         doc="Outlet isentropic enthalpy")
        def h_is(b, t):
            return te.h(s=properties_in[t].entr_mol,
                        p=properties_out[t].pressure)

        @self.Expression(self.flowsheet().config.time,
                         doc="Isentropic enthalpy change")
        def delta_enth_isentropic(b, t):
            return self.h_is[t] - properties_in[t].enth_mol

        @self.Expression(self.flowsheet().config.time, doc="Isentropic work")
        def work_isentropic(b, t):
            return properties_in[t].flow_mol * (properties_in[t].enth_mol -
                                                self.h_is[t])

        @self.Expression(self.flowsheet().config.time, doc="Outlet enthalpy")
        def h_o(b, t):  # Early access to the outlet enthalpy and work
            return properties_in[t].enth_mol - eff[t] * (
                properties_in[t].enth_mol - self.h_is[t])

        @self.Constraint(self.flowsheet().config.time)
        def eq_work(b, t):  # Work from energy balance
            return properties_out[t].enth_mol == self.h_o[t]

        @self.Constraint(self.flowsheet().config.time)
        def eq_pressure_ratio(b, t):
            return (pratio[t] *
                    properties_in[t].pressure == properties_out[t].pressure)

        @self.Expression(self.flowsheet().config.time)
        def work_mechanical(b, t):
            return b.control_volume.work[t]

    def _get_performance_contents(self, time_point=0):
        """This returns a dictionary of quntities to be used in IDAES unit model
        report generation routines.
        """
        pc = super()._get_performance_contents(time_point=time_point)
        return pc

    def initialize(
        self,
        outlvl=idaeslog.NOTSET,
        solver=None,
        optarg=None,
    ):
        """
        For simplicity this initialization requires you to set values for the
        efficency, inlet, and one of pressure ratio, pressure change or outlet
        pressure.
        """
        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")

        # Create solver
        slvr = get_solver(solver, optarg)

        # Store original specification so initialization doesn't change the model
        # This will only resore the values of varaibles that were originally fixed
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)
        # Check for alternate pressure specs
        for t in self.flowsheet().config.time:
            if self.outlet.pressure[t].fixed:
                self.ratioP[t] = pyo.value(self.outlet.pressure[t] /
                                           self.inlet.pressure[t])
            elif self.control_volume.deltaP[t].fixed:
                self.ratioP[t] = pyo.value(
                    (self.control_volume.deltaP[t] + self.inlet.pressure[t]) /
                    self.inlet.pressure[t])
        # Fix the variables we base the initializtion on and free the rest.
        # This requires good values to be provided for pressure, efficency,
        # and inlet conditions, but it is simple and reliable.
        self.inlet.fix()
        self.outlet.unfix()
        self.ratioP.fix()
        self.deltaP.unfix()
        self.efficiency_isentropic.fix()
        for t in self.flowsheet().config.time:
            self.outlet.pressure[t] = pyo.value(self.inlet.pressure[t] *
                                                self.ratioP[t])
            self.deltaP[t] = pyo.value(self.outlet.pressure[t] -
                                       self.inlet.pressure[t])

            self.outlet.enth_mol[t] = pyo.value(self.h_o[t])
            self.control_volume.work[t] = pyo.value(
                self.inlet.flow_mol[t] * self.inlet.enth_mol[t] -
                self.outlet.flow_mol[t] * self.outlet.enth_mol[t])
            self.outlet.flow_mol[t] = pyo.value(self.inlet.flow_mol[t])
        # Solve the model (should be already solved from above)
        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = slvr.solve(self, tee=slc.tee)
        from_json(self, sd=istate, wts=sp)

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

        for t, c in self.eq_pressure_ratio.items():
            s = iscale.get_scaling_factor(
                self.control_volume.properties_in[t].pressure)
            iscale.constraint_scaling_transform(c, s, overwrite=False)
        for t, c in self.eq_work.items():
            s = iscale.get_scaling_factor(self.control_volume.work[t])
            iscale.constraint_scaling_transform(c, s, overwrite=False)
Esempio n. 2
0
class HelmValveData(BalanceBlockData):
    """
    Basic adiabatic 0D valve model.  This inherits the balance block to get
    a lot of unit model boilerplate and the mass balance, enegy balance and
    pressure equations.  This model is intended to be used only with Helmholtz
    EOS property pacakges in mixed or single phase mode with P-H state vars.

    Since this inherits BalanceBlockData, and only operates in steady-state or
    pseudo-steady-state (for dynamic models) the following mass, energy and
    pressure equations are implicitly writen.

    1) Mass Balance:
        0 = flow_mol_in[t] - flow_mol_out[t]
    2) Energy Balance:
        0 = (flow_mol[t]*h_mol[t])_in - (flow_mol[t]*h_mol[t])_out
    3) Pressure:
        0 = P_in[t] + deltaP[t] - P_out[t]
    """

    CONFIG = BalanceBlockData.CONFIG()
    # For dynamics assume pseudo-steady-state
    CONFIG.dynamic = False
    CONFIG.get("dynamic")._default = False
    CONFIG.get("dynamic")._domain = In([False])
    CONFIG.has_holdup = False
    CONFIG.get("has_holdup")._default = False
    CONFIG.get("has_holdup")._domain = In([False])
    # Rest of config to make this function like a turbine
    CONFIG.has_pressure_change = True
    CONFIG.get("has_pressure_change")._default = True
    CONFIG.get("has_pressure_change")._domain = In([True])
    CONFIG.has_work_transfer = False
    CONFIG.get("has_work_transfer")._default = False
    CONFIG.get("has_work_transfer")._domain = In([False])
    CONFIG.has_heat_transfer = False
    CONFIG.get("has_heat_transfer")._default = False
    CONFIG.get("has_heat_transfer")._domain = In([False])
    CONFIG.declare(
        "valve_function",
        ConfigValue(
            default=ValveFunctionType.linear,
            domain=In(ValveFunctionType),
            description=
            "Valve function type, if custom provide an expression rule",
            doc=
            """The type of valve function, if custom provide an expression rule
with the valve_function_rule argument.
**default** - ValveFunctionType.linear
**Valid values** - {
ValveFunctionType.linear,
ValveFunctionType.quick_opening,
ValveFunctionType.equal_percentage,
ValveFunctionType.custom}""",
        ),
    )
    CONFIG.declare(
        "valve_function_callback",
        ConfigValue(
            default=None,
            description="This is a callback that adds a valve function.  The "
            "callback function takes the valve bock data argument.",
        ),
    )
    CONFIG.declare(
        "phase",
        ConfigValue(
            default="Vap",
            domain=In(("Vap", "Liq")),
            description='Expected phase of fluid in valve in {"Liq", "Vap"}',
        ),
    )

    def build(self):
        """
        Add model equations to the unit model.  This is called by a default block
        construnction rule when the unit model is created.
        """
        super().build()  # Basic unit model build/read config
        config = self.config  # shorter config pointer

        # The thermodynamic expression writer object, te, writes expressions
        # including external function calls to calculate thermodynamic quantities
        # from a set of state variables.
        _assert_properties(config.property_package)
        te = ThermoExpr(blk=self, parameters=config.property_package)

        self.valve_opening = pyo.Var(
            self.flowsheet().time,
            initialize=1,
            doc="Fraction open for valve from 0 to 1",
        )
        self.Cv = pyo.Var(initialize=0.1,
                          doc="Valve flow coefficent, for vapor "
                          "[mol/s/Pa] for liquid [mol/s/Pa]",
                          units=pyo.units.mol / pyo.units.s / pyo.units.Pa)
        #self.Cv.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.
        vfcb = self.config.valve_function_callback
        vfselect = self.config.valve_function
        if vfselect is not ValveFunctionType.custom and vfcb is not None:
            _log.warning(
                f"A valve function callback was provided but the valve "
                "function type is not custom.")

        if vfselect == ValveFunctionType.linear:
            _linear_callback(self)
        elif vfselect == ValveFunctionType.quick_opening:
            _quick_open_callback(self)
        elif vfselect == ValveFunctionType.equal_percentage:
            _equal_percentage_callback(self)
        else:
            if vfcb is None:
                raise ConfigurationError(
                    "No custom valve function callback provided")
            vfcb(self)

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

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

    def _get_performance_contents(self, time_point=0):
        """This returns a dictionary of quntities to be used in IDAES unit model
        report generation routines.
        """
        pc = super()._get_performance_contents(time_point=time_point)
        return pc

    def initialize(
        self,
        outlvl=idaeslog.NOTSET,
        solver=None,
        optarg=None,
    ):
        """
        For simplicity this initialization requires you to set values for the
        efficency, inlet, and one of pressure ratio, pressure change or outlet
        pressure.
        """
        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")

        # Create solver
        opt = get_solver(solver, optarg)

        # Store original specification so initialization doesn't change the model
        # This will only resore the values of varaibles that were originally fixed
        sp = StoreSpec.value_isfixed_isactive(only_fixed=True)
        istate = to_json(self, return_dict=True, wts=sp)
        # Check for alternate pressure specs
        for t in self.flowsheet().time:
            if self.outlet.pressure[t].fixed:
                self.deltaP[t].fix(
                    pyo.value(self.outlet.pressure[t] -
                              self.inlet.pressure[t]))
                self.outlet.pressure[t].unfix()
            elif self.deltaP[t].fixed:
                # No outlet pressure specified guess a small pressure drop
                self.outlet.pressure[t] = pyo.value(self.inlet.pressure[t] +
                                                    self.deltaP[t])

        self.inlet.fix()
        self.outlet.unfix()
        for t, v in self.deltaP.items():
            if v.fixed:
                self.inlet.flow_mol[t].unfix()

        with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
            res = opt.solve(self, tee=slc.tee)

        from_json(self, sd=istate, wts=sp)

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

        for t, c in self.pressure_flow_equation.items():
            s = iscale.get_scaling_factor(
                self.control_volume.properties_in[t].flow_mol)
            s = s**2
            iscale.constraint_scaling_transform(c, s, overwrite=False)