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