Ejemplo n.º 1
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)
Ejemplo n.º 2
0
class ValveData(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.material_balance_type = MaterialBalanceType.componentTotal
    CONFIG.get("material_balance_type")._default = \
        MaterialBalanceType.componentTotal
    CONFIG.thermodynamic_assumption = ThermodynamicAssumption.adiabatic
    CONFIG.get("thermodynamic_assumption")._default = \
        ThermodynamicAssumption.adiabatic
    CONFIG.get("thermodynamic_assumption")._domain = In(
        [ThermodynamicAssumption.adiabatic])
    CONFIG.declare(
        "valve_function_callback",
        ConfigValue(
            default=ValveFunctionType.linear,
            description="Valve function type or callback for custom",
            doc="""This takes either an enumerated valve function type in: {
ValveFunctionType.linear, ValveFunctionType.quick_opening,
ValveFunctionType.equal_percentage, ValveFunctionType.custom} or a callback
function that takes a valve model object as an argument and adds a time-indexed
valve_function expression to it. Any additional required variables, expressions,
or constraints required can also be added by the callback.""",
        ),
    )
    CONFIG.declare(
        "pressure_flow_callback",
        ConfigValue(
            default=pressure_flow_default_callback,
            description=
            "Callback function providing the valve_function expression",
            doc=
            """This callback function takes a valve model object as an argument
and adds a time-indexed valve_function expression to it.  Any additional required
variables, expressions, or constraints required can also be added by the callback.""",
        ),
    )

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

        self.valve_opening = pyo.Var(
            self.flowsheet().config.time,
            initialize=1,
            doc="Fraction open for valve from 0 to 1",
        )
        self.valve_opening.fix()
        # If the valve function callback is set to one of the known enumerated
        # types, set the callback appropriately.  If not callable and not a known
        # type raise ConfigurationError.
        vfcb = self.config.valve_function_callback
        if not callable(vfcb):
            if vfcb == ValveFunctionType.linear:
                self.config.valve_function_callback = linear_cb
            elif vfcb == ValveFunctionType.quick_opening:
                self.config.valve_function_callback = quick_cb
            elif vfcb == ValveFunctionType.equal_percentage:
                self.config.valve_function_callback = equal_percentage_cb
            else:
                raise ConfigurationError("Invalid valve function callback.")
        self.config.valve_function_callback(self)
        self.config.pressure_flow_callback(self)

    def initialize(
        self,
        state_args=None,
        outlvl=idaeslog.NOTSET,
        solver=None,
        optarg=None,
    ):
        """
        Initialize the valve based on a deltaP guess.

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

        for t in self.flowsheet().config.time:
            if (self.deltaP[t].fixed or self.ratioP[t].fixed
                    or self.outlet.pressure[t].fixed):
                continue
            # Generally for the valve initialization pressure drop won't be
            # fixed, so if there is no good guess on deltaP try to out one in
            Pout = self.outlet.pressure[t]
            Pin = self.inlet.pressure[t]
            if self.deltaP[t].value is not None:
                prdp = pyo.value((self.deltaP[t] - Pin) / Pin)
            else:
                prdp = -100  # crazy number to say don't use deltaP as guess
            if pyo.value(Pout / Pin) > 1 or pyo.value(Pout / Pin) < 0.0:
                if pyo.value(self.ratioP[t]) <= 1 and pyo.value(
                        self.ratioP[t]) >= 0:
                    Pout.value = pyo.value(Pin * self.ratioP[t])
                elif prdp <= 1 and prdp >= 0:
                    Pout.value = pyo.value(prdp * Pin)
                else:
                    Pout.value = pyo.value(Pin * 0.95)
            self.deltaP[t] = pyo.value(Pout - Pin)
            self.ratioP[t] = pyo.value(Pout / Pin)

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

    def calculate_scaling_factors(self):
        """
        Calculate pressure flow constraint scaling from flow variable scale.
        """
        # The value of the valve opening and the output of the valve function
        # expression are between 0 and 1, so the only thing that needs to be
        # scaled here is the pressure-flow constraint, which can be scaled by
        # using the flow variable scale.  The flow variable could be defined
        # in different ways, so the flow variable is determined here from a
        # "flow_var[t]" reference set in the pressure-flow callback. The flow
        # term could be in various forms, so an optional
        # "pressure_flow_equation_scale" function can be defined in the callback
        # as well.  The pressure-flow function could be flow = f(Pin, Pout), but
        # it could also be flow**2 = f(Pin, Pout), ... The so
        # "pressure_flow_equation_scale" provides the form of the LHS side as
        # a function of the flow variable.

        super().calculate_scaling_factors()

        # Do some error trapping.
        if not hasattr(self, "pressure_flow_equation"):
            raise AttributeError(
                "Pressure-flow callback must define pressure_flow_equation[t]")
        # Check for flow term form if none assume flow = f(Pin, Pout)
        if hasattr(self, "pressure_flow_equation_scale"):
            ff = self.pressure_flow_equation_scale
        else:
            ff = lambda x: x
        # if the "flow_var" is not set raise an exception
        if not hasattr(self, "flow_var"):
            raise AttributeError(
                "Pressure-flow callback must define flow_var[t] reference")

        # Calculate and set the pressure-flow relation scale.
        if hasattr(self, "pressure_flow_equation"):
            for t, c in self.pressure_flow_equation.items():
                iscale.constraint_scaling_transform(
                    c,
                    ff(
                        iscale.get_scaling_factor(self.flow_var[t],
                                                  default=1,
                                                  warning=True)))

    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]
        try:
            pc["vars"]["Valve Coefficient"] = self.Cv
        except AttributeError:
            pass
        if self.config.valve_function == ValveFunctionType.equal_percentage:
            pc["vars"]["alpha"] = self.alpha
        return pc
Ejemplo n.º 3
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)
Ejemplo n.º 4
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=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
        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)

        # 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)
Ejemplo n.º 5
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=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