Source code for idaes.models.properties.general_helmholtz.helmholtz_state

#################################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2024 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved.  Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
#################################################################################
"""Generic Helmholtz EOS StateBlock Class
"""

__author__ = "John Eslick"

import copy

import pyomo.environ as pyo
from pyomo.common.collections import ComponentSet
from pyomo.environ import units as pyunits

from idaes.core.util.math import smooth_max
from idaes.core import declare_process_block_class
from idaes.core import (
    StateBlock,
    StateBlockData,
    MaterialBalanceType,
    EnergyBalanceType,
    MaterialFlowBasis,
)
from idaes.models.properties.general_helmholtz.helmholtz_functions import (
    add_helmholtz_external_functions,
    HelmholtzThermoExpressions,
    AmountBasis,
    PhaseType,
    StateVars,
    _data_dir,
)
from idaes.models.properties.general_helmholtz.components import (
    viscosity_available,
    thermal_conductivity_available,
    surface_tension_available,
)
import idaes.core.util.scaling as iscale
import idaes.logger as idaeslog
from idaes.core.util.initialization import fix_state_vars
from idaes.core.initialization.initializer_base import (
    InitializerBase,
    InitializationStatus,
)

_log = idaeslog.getLogger(__name__)


[docs] class HelmholtzEoSInitializer(InitializerBase): """ Initializer object for Helmholtz EoS packages using external functions. Due to the use of external functions, Helmholtz EoS StateBlocks have no constraints, thus there is nothing to initialize. This Initializer replaces the general initialize method with a no-op. """ CONFIG = InitializerBase.CONFIG()
[docs] def initialize( self, model: pyo.Block, output_level=None, ): """ Initialize method for Helmholtz EoS state blocks. This is a no-op. Args: model: model to be initialized output_level: (optional) output level to use during initialization run (overrides global setting). Returns: InitializationStatus.Ok """ self._update_summary(model, "status", InitializationStatus.Ok) return self.summary[model]["status"]
class _StateBlock(StateBlock): """ This class contains methods which should be applied to Property Blocks as a whole, rather than individual elements of indexed Property Blocks. """ # Set default initializer default_initializer = HelmholtzEoSInitializer @staticmethod def _set_fixed(v, f): if f: v.fix() else: v.unfix() @staticmethod def _set_not_fixed(v, state, key, hold): if state is not None: if not v.fixed: try: v.value = state[key] except KeyError: pass if hold: v.fix() def fix_initialization_states(self): """ Fixes state variables for state blocks. Returns: None """ # Fix state variables fix_state_vars(self) def initialize(self, *args, **kwargs): flags = {} hold_state = kwargs.pop("hold_state", False) state_args = kwargs.pop("state_args", None) for i, v in self.items(): pp = self[i].config.parameters.config.phase_presentation sv = self[i].state_vars ab = self[i].amount_basis if sv == StateVars.PH and ab == AmountBasis.MOLE: flags[i] = (v.flow_mol.fixed, v.enth_mol.fixed, v.pressure.fixed) self._set_not_fixed(v.flow_mol, state_args, "flow_mol", hold_state) self._set_not_fixed(v.enth_mol, state_args, "enth_mol", hold_state) self._set_not_fixed(v.pressure, state_args, "pressure", hold_state) elif sv == StateVars.PH and ab == AmountBasis.MASS: flags[i] = (v.flow_mass.fixed, v.enth_mass.fixed, v.pressure.fixed) self._set_not_fixed(v.flow_mass, state_args, "flow_mass", hold_state) self._set_not_fixed(v.enth_mass, state_args, "enth_mass", hold_state) self._set_not_fixed(v.pressure, state_args, "pressure", hold_state) elif sv == StateVars.PS and ab == AmountBasis.MOLE: flags[i] = (v.flow_mol.fixed, v.entr_mol.fixed, v.pressure.fixed) self._set_not_fixed(v.flow_mol, state_args, "flow_mol", hold_state) self._set_not_fixed(v.entr_mol, state_args, "enth_mol", hold_state) self._set_not_fixed(v.pressure, state_args, "pressure", hold_state) elif sv == StateVars.PS and ab == AmountBasis.MASS: flags[i] = (v.flow_mass.fixed, v.entr_mass.fixed, v.pressure.fixed) self._set_not_fixed(v.flow_mass, state_args, "flow_mass", hold_state) self._set_not_fixed(v.entr_mass, state_args, "enth_mass", hold_state) self._set_not_fixed(v.pressure, state_args, "pressure", hold_state) elif sv == StateVars.PU and ab == AmountBasis.MOLE: flags[i] = ( v.flow_mol.fixed, v.energy_internal_mol.fixed, v.pressure.fixed, ) self._set_not_fixed( v.energy_internal_mol, state_args, "energy_internal_mol", hold_state ) self._set_not_fixed( v.energy_internal_mol, state_args, "energy_internal_mol", hold_state ) self._set_not_fixed( v.energy_internal_mol, state_args, "energy_internal_mol", hold_state ) elif sv == StateVars.PU and ab == AmountBasis.MASS: flags[i] = ( v.flow_mass.fixed, v.energy_internal_mass.fixed, v.pressure.fixed, ) self._set_not_fixed( v.energy_internal_mass, state_args, "energy_internal_mass", hold_state, ) self._set_not_fixed( v.energy_internal_mass, state_args, "energy_internal_mass", hold_state, ) self._set_not_fixed(v.pressure, state_args, "pressure", hold_state) elif sv == StateVars.TPX and ab == AmountBasis.MOLE: # Hold the T-P-x vars if pp in (PhaseType.MIX, PhaseType.LG): flags[i] = ( v.flow_mol.fixed, v.temperature.fixed, v.pressure.fixed, v.vapor_frac.fixed, ) self._set_not_fixed(v.flow_mol, state_args, "flow_mol", hold_state) self._set_not_fixed( v.temperature, state_args, "temperature", hold_state ) self._set_not_fixed(v.pressure, state_args, "pressure", hold_state) self._set_not_fixed( v.vapor_frac, state_args, "vapor_frac", hold_state ) else: flags[i] = (v.flow_mol.fixed, v.temperature.fixed, v.pressure.fixed) self._set_not_fixed(v.flow_mol, state_args, "flow_mol", hold_state) self._set_not_fixed( v.temperature, state_args, "temperature", hold_state ) self._set_not_fixed(v.pressure, state_args, "pressure", hold_state) elif sv == StateVars.TPX and ab == AmountBasis.MASS: # Hold the T-P-x vars if pp in (PhaseType.MIX, PhaseType.LG): flags[i] = ( v.flow_mass.fixed, v.temperature.fixed, v.pressure.fixed, v.vapor_frac.fixed, ) self._set_not_fixed( v.flow_mass, state_args, "flow_mass", hold_state ) self._set_not_fixed( v.temperature, state_args, "temperature", hold_state ) self._set_not_fixed(v.pressure, state_args, "pressure", hold_state) self._set_not_fixed( v.vapor_frac, state_args, "vapor_frac", hold_state ) else: flags[i] = ( v.flow_mass.fixed, v.temperature.fixed, v.pressure.fixed, ) self._set_not_fixed( v.flow_mass, state_args, "flow_mass", hold_state ) self._set_not_fixed( v.temperature, state_args, "temperature", hold_state ) self._set_not_fixed(v.pressure, state_args, "pressure", hold_state) return flags def release_state(self, flags, **kwargs): """Set the variables back to there original fixed/free state after initialization. Args: flags (dict): Original variable states """ for i, f in flags.items(): pp = self[i].config.parameters.config.phase_presentation sv = self[i].state_vars ab = self[i].amount_basis if sv == StateVars.PH and ab == AmountBasis.MOLE: self._set_fixed(self[i].flow_mol, f[0]) self._set_fixed(self[i].enth_mol, f[1]) self._set_fixed(self[i].pressure, f[2]) elif sv == StateVars.PH and ab == AmountBasis.MASS: self._set_fixed(self[i].flow_mass, f[0]) self._set_fixed(self[i].enth_mass, f[1]) self._set_fixed(self[i].pressure, f[2]) elif sv == StateVars.PS and ab == AmountBasis.MOLE: self._set_fixed(self[i].flow_mol, f[0]) self._set_fixed(self[i].entr_mol, f[1]) self._set_fixed(self[i].pressure, f[2]) elif sv == StateVars.PS and ab == AmountBasis.MASS: self._set_fixed(self[i].flow_mass, f[0]) self._set_fixed(self[i].entr_mass, f[1]) self._set_fixed(self[i].pressure, f[2]) elif sv == StateVars.PU and ab == AmountBasis.MOLE: self._set_fixed(self[i].flow_mol, f[0]) self._set_fixed(self[i].energy_internal_mol, f[1]) self._set_fixed(self[i].pressure, f[2]) elif sv == StateVars.PU and ab == AmountBasis.MASS: self._set_fixed(self[i].flow_mass, f[0]) self._set_fixed(self[i].energy_internal_mass, f[1]) self._set_fixed(self[i].pressure, f[2]) elif sv == StateVars.TPX and ab == AmountBasis.MOLE: self._set_fixed(self[i].flow_mol, f[0]) self._set_fixed(self[i].temperature, f[1]) self._set_fixed(self[i].pressure, f[2]) if pp in (PhaseType.MIX, PhaseType.LG): self._set_fixed(self[i].vapor_frac, f[3]) elif sv == StateVars.TPX and ab == AmountBasis.MASS: self._set_fixed(self[i].flow_mass, f[0]) self._set_fixed(self[i].temperature, f[1]) self._set_fixed(self[i].pressure, f[2]) if pp in (PhaseType.MIX, PhaseType.LG): self._set_fixed(self[i].vapor_frac, f[3])
[docs] @declare_process_block_class("HelmholtzStateBlock", block_class=_StateBlock) class HelmholtzStateBlockData(StateBlockData): """ This is a base class for Helmholtz equations of state using IDAES standard Helmholtz EOS external functions written in C++. """ def _state_vars(self): """Create the state variables""" params = self.config.parameters cmp = params.pure_component phase_set = params.config.phase_presentation # Check for inconsistent phase presentation and phase equilibrium if phase_set == PhaseType.MIX and self.config.has_phase_equilibrium: _log.warning( "Helmholtz EoS packages using Mixed phase representation ignore the " "'has_phase_equilibrium' configuration argument. However, setting " "this to True can result in errors when constructing material balances " "due to only having a single phase (thus phase transfer terms cannot " "be constructed)." ) # Add flow variable if self.amount_basis == AmountBasis.MOLE: self.flow_mol = pyo.Var( initialize=1, doc="Total mole flow", units=pyunits.mol / pyunits.s ) self.flow_mass = pyo.Expression( expr=self.mw * self.flow_mol, doc="Total mass flow" ) else: self.flow_mass = pyo.Var( initialize=1, doc="Total mass flow", units=pyunits.kg / pyunits.s ) self.flow_mol = pyo.Expression( expr=self.flow_mass / self.mw, doc="Total mole flow" ) # All supported state variable sets include pressure self.pressure = pyo.Var( domain=pyo.PositiveReals, initialize=params.default_pressure_value, doc="Pressure", bounds=params.default_pressure_bounds, units=pyunits.Pa, ) self.p_kPa = pyo.Expression(expr=self.pressure * params.uc["Pa to kPa"]) # If it's single phase use provide fixed expressions for vapor frac if phase_set == PhaseType.L: self.vapor_frac = pyo.Expression( expr=0.0, doc="Vapor mole fraction (mol vapor/mol total)" ) elif phase_set == PhaseType.G: self.vapor_frac = pyo.Expression( expr=1.0, doc="Vapor mole fraction (mol vapor/mol total)", ) # Add other state var if self.state_vars == StateVars.PH and self.amount_basis == AmountBasis.MOLE: self.enth_mol = pyo.Var( initialize=params.default_enthalpy_mol_value, doc="Total molar enthalpy", bounds=params.default_enthalpy_mol_bounds, units=pyunits.J / pyunits.mol, ) self.h_kJ_per_kg = pyo.Expression( expr=self.enth_mol * params.uc["J/mol to kJ/kg"] ) self.temperature = pyo.Expression( expr=self.t_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir), doc="Temperature", ) if phase_set == PhaseType.MIX or phase_set == PhaseType.LG: self.vapor_frac = pyo.Expression( expr=self.vf_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir), doc="Vapor mole fraction (mol vapor/mol total)", ) self._state_vars_dict = { "flow_mol": self.flow_mol, "enth_mol": self.enth_mol, "pressure": self.pressure, } self.extensive_set = ComponentSet((self.flow_mol,)) self.intensive_set = ComponentSet((self.enth_mol, self.pressure)) elif self.state_vars == StateVars.PH and self.amount_basis == AmountBasis.MASS: self.enth_mass = pyo.Var( initialize=params.default_enthalpy_mass_value, doc="Total enthalpy per mass", bounds=params.default_enthalpy_mass_bounds, units=pyunits.J / pyunits.kg, ) self.h_kJ_per_kg = pyo.Expression( expr=self.enth_mass * params.uc["J/kg to kJ/kg"] ) self.temperature = pyo.Expression( expr=self.t_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir), doc="Temperature", ) if phase_set == PhaseType.MIX or phase_set == PhaseType.LG: self.vapor_frac = pyo.Expression( expr=self.vf_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir), doc="Vapor mole fraction (mol vapor/mol total)", ) self._state_vars_dict = { "flow_mass": self.flow_mass, "enth_mass": self.enth_mass, "pressure": self.pressure, } self.extensive_set = ComponentSet((self.flow_mass,)) self.intensive_set = ComponentSet((self.enth_mass, self.pressure)) elif self.state_vars == StateVars.PS and self.amount_basis == AmountBasis.MOLE: self.entr_mol = pyo.Var( initialize=params.default_entropy_mol_value, doc="Total molar entropy", bounds=params.default_entropy_mol_bounds, units=pyunits.J / pyunits.mol / pyunits.K, ) self.s_kJ_per_kgK = pyo.Expression( expr=self.entr_mol * params.uc["J/mol/K to kJ/kg/K"] ) self.temperature = pyo.Expression( expr=self.t_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir), doc="Temperature", ) if phase_set == PhaseType.MIX or phase_set == PhaseType.LG: self.vapor_frac = pyo.Expression( expr=self.vf_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir), doc="Vapor mole fraction (mol vapor/mol total)", ) self._state_vars_dict = { "flow_mol": self.flow_mol, "entr_mol": self.entr_mol, "pressure": self.pressure, } self.extensive_set = ComponentSet((self.flow_mol,)) self.intensive_set = ComponentSet((self.entr_mol, self.pressure)) elif self.state_vars == StateVars.PS and self.amount_basis == AmountBasis.MASS: self.entr_mass = pyo.Var( initialize=params.default_entropy_mass_value, doc="Total entropy per mass", bounds=params.default_entropy_mass_bounds, units=pyunits.J / pyunits.kg / pyunits.K, ) self.s_kJ_per_kgK = pyo.Expression( expr=self.entr_mass * params.uc["J/kg/K to kJ/kg/K"] ) self.temperature = pyo.Expression( expr=self.t_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir), doc="Temperature", ) if phase_set == PhaseType.MIX or phase_set == PhaseType.LG: self.vapor_frac = pyo.Expression( expr=self.vf_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir), doc="Vapor mole fraction (mol vapor/mol total)", ) self._state_vars_dict = { "flow_mass": self.flow_mass, "entr_mass": self.entr_mass, "pressure": self.pressure, } self.extensive_set = ComponentSet((self.flow_mass,)) self.intensive_set = ComponentSet((self.entr_mass, self.pressure)) elif self.state_vars == StateVars.PU and self.amount_basis == AmountBasis.MOLE: self.energy_internal_mol = pyo.Var( initialize=params.default_energy_internal_mol_value, doc="Total molar internal energy", bounds=params.default_energy_internal_mol_bounds, units=pyunits.J / pyunits.mol, ) self.u_kJ_per_kg = pyo.Expression( expr=self.energy_internal_mol * params.uc["J/mol to kJ/kg"] ) self.temperature = pyo.Expression( expr=self.t_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir), doc="Temperature", ) if phase_set == PhaseType.MIX or phase_set == PhaseType.LG: self.vapor_frac = pyo.Expression( expr=self.vf_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir), doc="Vapor mole fraction (mol vapor/mol total)", ) self._state_vars_dict = { "flow_mol": self.flow_mol, "energy_internal_mol": self.energy_internal_mol, "pressure": self.pressure, } self.extensive_set = ComponentSet((self.flow_mol,)) self.intensive_set = ComponentSet((self.energy_internal_mol, self.pressure)) elif self.state_vars == StateVars.PU and self.amount_basis == AmountBasis.MASS: self.energy_internal_mass = pyo.Var( initialize=params.default_energy_internal_mass_value, doc="Total internal energy per mass", bounds=params.default_energy_internal_mass_bounds, units=pyunits.J / pyunits.kg, ) self.u_kJ_per_kg = pyo.Expression( expr=self.energy_internal_mass * params.uc["J/kg to kJ/kg"] ) self.temperature = pyo.Expression( expr=self.t_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir), doc="Temperature", ) if phase_set == PhaseType.MIX or phase_set == PhaseType.LG: self.vapor_frac = pyo.Expression( expr=self.vf_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir), doc="Vapor mole fraction (mol vapor/mol total)", ) self._state_vars_dict = { "flow_mass": self.flow_mass, "energy_internal_mass": self.energy_internal_mass, "pressure": self.pressure, } self.extensive_set = ComponentSet((self.flow_mass,)) self.intensive_set = ComponentSet( (self.energy_internal_mass, self.pressure) ) if self.state_vars == StateVars.TPX: self.temperature = pyo.Var( domain=pyo.PositiveReals, initialize=params.default_temperature_value, doc="Temperature", bounds=params.default_temperature_bounds, units=pyunits.K, ) if phase_set == PhaseType.MIX or phase_set == PhaseType.LG: self.vapor_frac = pyo.Var( initialize=0.0, units=pyunits.dimensionless, doc="Vapor fraction", # No bounds here, since it is often (usually) on it's bound # and that's not the best for IPOPT ) self.intensive_set = ComponentSet( (self.temperature, self.pressure, self.vapor_frac) ) self._state_vars_dict = { "temperature": self.temperature, "pressure": self.pressure, "vapor_frac": self.vapor_frac, } else: self.intensive_set = ComponentSet((self.temperature, self.pressure)) self._state_vars_dict = { "temperature": self.temperature, "pressure": self.pressure, } if self.amount_basis == AmountBasis.MOLE: self.extensive_set = ComponentSet((self.flow_mol,)) self._state_vars_dict["flow_mol"] = self.flow_mol else: self.extensive_set = ComponentSet((self.flow_mass,)) self._state_vars_dict["flow_mass"] = self.flow_mass def _tpx_phase_eq(self): params = self.config.parameters eps_pu = params.smoothing_pressure_under eps_po = params.smoothing_pressure_over priv_plist = params.private_phase_list # Convert pressures to kPa for external functions and nicer scaling in # the complementarity-type constraints. vf = self.vapor_frac # Terms for determining if you are above, below, or at the Psat self.pressure_under_sat = pyo.Expression( expr=smooth_max(0, self.pressure_sat - self.pressure, eps_pu), doc="pressure above Psat, 0 if liquid exists", ) self.pressure_over_sat = pyo.Expression( expr=smooth_max(0, self.pressure - self.pressure_sat, eps_po), doc="pressure below Psat, 0 if vapor exists", ) if not self.config.defined_state: self.eq_complementarity = pyo.Constraint( expr=0 == ( vf * self.pressure_over_sat / 1000.0 - (1 - vf) * self.pressure_under_sat / 1000.0 ) ) # Calculate liquid and vapor density. If the phase doesn't exist, # density will be calculated at the saturation or critical pressure def rule_pressure_phase(b, p): if p == "Liq": return self.pressure + self.pressure_under_sat else: return self.pressure - self.pressure_over_sat self.pressure_phase = pyo.Expression(priv_plist, rule=rule_pressure_phase) # Constraint that can be activated to enforce that you are in the two-phase region self.eq_sat = pyo.Constraint( expr=self.pressure * 1e-6 == self.pressure_sat * 1e-6 ) self.eq_sat.deactivate()
[docs] def build(self, *args): """ Callable method for Block construction """ # Call base class build super().build(*args) # Short path to the parameter block params = self.config.parameters # Check if the library is available, and add external functions. add_helmholtz_external_functions(self) cmp = params.pure_component # Which state vars to use self.state_vars = params.state_vars # Mass or Mole basis self.amount_basis = params.config.amount_basis # Private phase list phlist = params.private_phase_list # Public phase list pub_phlist = params.phase_list component_list = params.component_list self.phase_equilibrium_list = params.phase_equilibrium_list # Add component mole fraction for standardization def mole_frac_comp_rule(b, i): return 1.0 self.mole_frac_comp = pyo.Expression(component_list, rule=mole_frac_comp_rule) # 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 = pyo.Expression( expr=params.temperature_crit, doc="critical temperature" ) self.temperature_star = pyo.Expression( expr=params.temperature_star, doc="temperature for tau calculation" ) self.pressure_crit = pyo.Expression( expr=params.pressure_crit, doc="critical pressure" ) self.dens_mass_crit = pyo.Expression( expr=params.dens_mass_crit, doc="critical mass density" ) self.dens_mass_star = pyo.Expression( expr=params.dens_mass_star, doc="mass density for delta calculation" ) self.dens_mol_crit = pyo.Expression( expr=params.dens_mass_crit / params.mw, doc="critical mole density" ) self.dens_mol_star = pyo.Expression( expr=params.dens_mass_star / params.mw, doc="mole density for delta calculation", ) self.mw = pyo.Expression(expr=params.mw, doc="molecular weight") # create the appropriate state variables and expressions for anything # that could be a state variable (H, S, U, P, T, X) on a mass and mole # basis if using TPx as state variables it also adds the complementarity # constraints. Beyond this everything else is common. self._state_vars() # P is pressure in kPa for external function calls T = self.temperature vf = self.vapor_frac # Saturation temperature expression self.temperature_sat = pyo.Expression( expr=self.t_sat_func(cmp, self.p_kPa, _data_dir), doc="Saturation temperature", ) # Saturation pressure self.pressure_sat = pyo.Expression( expr=self.p_sat_t_func(cmp, T, _data_dir) * params.uc["kPa to Pa"], doc="Saturation pressure", ) # Add the complementarity constraint for phase equilibrium with TPx. if self.state_vars == StateVars.TPX and len(phlist) > 1: self._tpx_phase_eq() # to make writing the remaining expressions simpler create a state var dict if self.state_vars == StateVars.PH: sv_dict = {"h": self.h_kJ_per_kg, "p": self.p_kPa} elif self.state_vars == StateVars.PS: sv_dict = {"s": self.s_kJ_per_kgK, "p": self.p_kPa} elif self.state_vars == StateVars.PU: sv_dict = {"u": self.u_kJ_per_kg, "p": self.p_kPa} elif self.state_vars == StateVars.TPX: sv_dict = { "T": self.temperature, "p": self.p_kPa, "x": self.vapor_frac, } sv_dict_liq = copy.copy(sv_dict) sv_dict_vap = copy.copy(sv_dict) if self.state_vars == StateVars.TPX and len(phlist) > 1: self.p_kPa_liq = pyo.Expression( expr=self.pressure_phase["Liq"] * params.uc["Pa to kPa"] ) self.p_kPa_vap = pyo.Expression( expr=self.pressure_phase["Vap"] * params.uc["Pa to kPa"] ) sv_dict_liq["p"] = self.p_kPa_liq sv_dict_vap["p"] = self.p_kPa_vap self.expression_writer = HelmholtzThermoExpressions(self, params) # Saturated Enthalpy molar def rule_enth_mol_sat_phase(b, p): if p == "Liq": return self.expression_writer.h_liq_sat( p=b.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) else: return self.expression_writer.h_vap_sat( p=b.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) self.enth_mol_sat_phase = pyo.Expression( phlist, rule=rule_enth_mol_sat_phase, doc="Saturated enthalpy of the phases at pressure", ) # Saturated Enthalpy mass def rule_enth_mass_sat_phase(b, p): if p == "Liq": return self.expression_writer.h_liq_sat( p=b.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) else: return self.expression_writer.h_vap_sat( p=b.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) self.enth_mass_sat_phase = pyo.Expression( phlist, rule=rule_enth_mass_sat_phase, doc="Saturated enthalpy of the phases at pressure", ) # Saturated Entropy molar def rule_entr_mol_sat_phase(b, p): if p == "Liq": return self.expression_writer.s_liq_sat( p=b.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) else: return self.expression_writer.s_vap_sat( p=b.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) self.entr_mol_sat_phase = pyo.Expression( phlist, rule=rule_entr_mol_sat_phase, doc="Saturated entropy of the phases at pressure", ) # Saturated Entropy mass def rule_entr_mass_sat_phase(b, p): if p == "Liq": return self.expression_writer.s_liq_sat( p=b.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) else: return self.expression_writer.s_vap_sat( p=b.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) self.entr_mass_sat_phase = pyo.Expression( phlist, rule=rule_entr_mass_sat_phase, doc="Saturated entropy of the phases at pressure", ) # Saturated internal energy molar def rule_energy_internal_mol_sat_phase(b, p): if p == "Liq": return self.expression_writer.u_liq_sat( p=b.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) else: return self.expression_writer.u_vap_sat( p=b.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) self.energy_internal_mol_sat_phase = pyo.Expression( phlist, rule=rule_energy_internal_mol_sat_phase, doc="Saturated internal energy of the phases at pressure", ) # Saturated internal energy mass def rule_energy_internal_mass_sat_phase(b, p): if p == "Liq": return self.expression_writer.u_liq_sat( p=b.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) else: return self.expression_writer.u_vap_sat( p=b.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) self.energy_internal_mass_sat_phase = pyo.Expression( phlist, rule=rule_energy_internal_mass_sat_phase, doc="Saturated internal energy of the phases at pressure", ) # Saturated molar volume def rule_volume_mol_sat_phase(b, p): if p == "Liq": return self.expression_writer.v_liq_sat( p=self.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) else: return self.expression_writer.v_vap_sat( p=self.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) self.volume_mol_sat_phase = pyo.Expression( phlist, rule=rule_volume_mol_sat_phase, doc="Saturated molar volume of the phases at pressure", ) # Saturated specific volume def rule_volume_mass_sat_phase(b, p): if p == "Liq": return self.expression_writer.v_liq_sat( p=self.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) else: return self.expression_writer.v_vap_sat( p=self.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) self.volume_mass_sat_phase = pyo.Expression( phlist, rule=rule_volume_mass_sat_phase, doc="Saturated specific volume of the phases at pressure", ) # delta h vap at P self.dh_vap_mol = pyo.Expression( expr=( self.expression_writer.h_vap_sat( p=self.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) - self.expression_writer.h_liq_sat( p=self.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) ), doc="Enthalpy of vaporization at pressure and saturation temperature", ) # delta s vap at P self.ds_vap_mol = pyo.Expression( expr=( self.expression_writer.s_vap_sat( p=self.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) - self.expression_writer.s_liq_sat( p=self.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) ), doc="Entropy of vaporization at pressure and saturation temperature", ) # delta u vap at P self.du_vap_mol = pyo.Expression( expr=( self.expression_writer.u_vap_sat( p=self.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) - self.expression_writer.u_liq_sat( p=self.p_kPa, result_basis=AmountBasis.MOLE, convert_args=False ) ), doc="Internal energy of vaporization at pressure and saturation temperature", ) # delta h vap at P self.dh_vap_mass = pyo.Expression( expr=( self.expression_writer.h_vap_sat( p=self.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) - self.expression_writer.h_liq_sat( p=self.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) ), doc="Enthalpy of vaporization at pressure and saturation temperature", ) # delta s vap at P self.ds_vap_mass = pyo.Expression( expr=( self.expression_writer.s_vap_sat( p=self.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) - self.expression_writer.s_liq_sat( p=self.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) ), doc="Entropy of vaporization at pressure and saturation temperature", ) # delta u vap at P self.du_vap_mass = pyo.Expression( expr=( self.expression_writer.u_vap_sat( p=self.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) - self.expression_writer.u_liq_sat( p=self.p_kPa, result_basis=AmountBasis.MASS, convert_args=False ) ), doc="Internal energy of vaporization at pressure and saturation temperature", ) # Phase fraction def rule_phase_frac(b, p): if p == "Vap": return vf elif p == "Liq": return 1.0 - vf self.phase_frac = pyo.Expression( phlist, rule=rule_phase_frac, doc="Phase fraction" ) def rule_energy_internal_mol_phase(b, p): if p == "Liq": return self.expression_writer.u_liq( **sv_dict_liq, result_basis=AmountBasis.MOLE, convert_args=False ) else: return self.expression_writer.u_vap( **sv_dict_vap, result_basis=AmountBasis.MOLE, convert_args=False ) self.energy_internal_mol_phase = pyo.Expression( phlist, rule=rule_energy_internal_mol_phase, doc="Phase internal energy", ) def rule_energy_internal_mass_phase(b, p): if p == "Liq": return self.expression_writer.u_liq( **sv_dict_liq, result_basis=AmountBasis.MASS, convert_args=False ) else: return self.expression_writer.u_vap( **sv_dict_vap, result_basis=AmountBasis.MASS, convert_args=False ) self.energy_internal_mass_phase = pyo.Expression( phlist, rule=rule_energy_internal_mass_phase, doc="Phase internal energy", ) # Phase Enthalpy def rule_enth_mol_phase(b, p): if p == "Liq": return self.expression_writer.h_liq( **sv_dict_liq, result_basis=AmountBasis.MOLE, convert_args=False ) else: return self.expression_writer.h_vap( **sv_dict_vap, result_basis=AmountBasis.MOLE, convert_args=False ) self.enth_mol_phase = pyo.Expression( phlist, rule=rule_enth_mol_phase, doc="Phase enthalpy", ) def rule_enth_mass_phase(b, p): if p == "Liq": return self.expression_writer.h_liq( **sv_dict_liq, result_basis=AmountBasis.MASS, convert_args=False ) else: return self.expression_writer.h_vap( **sv_dict_vap, result_basis=AmountBasis.MASS, convert_args=False ) self.enth_mass_phase = pyo.Expression( phlist, rule=rule_enth_mass_phase, doc="Phase enthalpy", ) # Phase Entropy def rule_entr_mol_phase(b, p): if p == "Liq": return self.expression_writer.s_liq( **sv_dict_liq, result_basis=AmountBasis.MOLE, convert_args=False ) else: return self.expression_writer.s_vap( **sv_dict_vap, result_basis=AmountBasis.MOLE, convert_args=False ) self.entr_mol_phase = pyo.Expression( phlist, rule=rule_entr_mol_phase, doc="Phase entropy", ) def rule_entr_mass_phase(b, p): if p == "Liq": return self.expression_writer.s_liq( **sv_dict_liq, result_basis=AmountBasis.MASS, convert_args=False ) else: return self.expression_writer.s_vap( **sv_dict_vap, result_basis=AmountBasis.MASS, convert_args=False ) self.entr_mass_phase = pyo.Expression( phlist, rule=rule_entr_mass_phase, doc="Phase entropy", ) # Phase constant pressure heat capacity, cp def rule_cp_mol_phase(b, p): if p == "Liq": return self.expression_writer.cp_liq( **sv_dict_liq, result_basis=AmountBasis.MOLE, convert_args=False ) else: return self.expression_writer.cp_vap( **sv_dict_vap, result_basis=AmountBasis.MOLE, convert_args=False ) self.cp_mol_phase = pyo.Expression( phlist, rule=rule_cp_mol_phase, doc="Phase isobaric heat capacity", ) def rule_cp_mass_phase(b, p): if p == "Liq": return self.expression_writer.cp_liq( **sv_dict_liq, result_basis=AmountBasis.MASS, convert_args=False ) else: return self.expression_writer.cp_vap( **sv_dict_vap, result_basis=AmountBasis.MASS, convert_args=False ) self.cp_mass_phase = pyo.Expression( phlist, rule=rule_cp_mass_phase, doc="Phase isobaric heat capacity", ) # Phase constant volume heat capacity, cv def rule_cv_mol_phase(b, p): if p == "Liq": return self.expression_writer.cv_liq( **sv_dict_liq, result_basis=AmountBasis.MOLE, convert_args=False ) else: return self.expression_writer.cv_vap( **sv_dict_vap, result_basis=AmountBasis.MOLE, convert_args=False ) self.cv_mol_phase = pyo.Expression( phlist, rule=rule_cv_mol_phase, doc="Phase isochoric heat capacity", ) def rule_cv_mass_phase(b, p): if p == "Liq": return self.expression_writer.cv_liq( **sv_dict_liq, result_basis=AmountBasis.MASS, convert_args=False ) else: return self.expression_writer.cv_vap( **sv_dict_vap, result_basis=AmountBasis.MASS, convert_args=False ) self.cv_mass_phase = pyo.Expression( phlist, rule=rule_cv_mass_phase, doc="Phase isochoric heat capacity", ) # Phase speed of sound def rule_speed_sound_phase(b, p): if p == "Liq": return self.expression_writer.w_liq(**sv_dict_liq, convert_args=False) else: return self.expression_writer.w_vap(**sv_dict_liq, convert_args=False) self.speed_sound_phase = pyo.Expression( phlist, rule=rule_speed_sound_phase, doc="Phase speed of sound or saturated if phase doesn't exist", ) # molar volume def rule_vol_mol_phase(b, p): if p == "Liq": return self.expression_writer.v_liq( **sv_dict_liq, result_basis=AmountBasis.MOLE, convert_args=False ) else: return self.expression_writer.v_vap( **sv_dict_vap, result_basis=AmountBasis.MOLE, convert_args=False ) self.vol_mol_phase = pyo.Expression( phlist, rule=rule_vol_mol_phase, doc="Molar volume of phase", ) # specific volume def rule_vol_mass_phase(b, p): if p == "Liq": return self.expression_writer.v_liq( **sv_dict_liq, result_basis=AmountBasis.MASS, convert_args=False ) else: return self.expression_writer.v_vap( **sv_dict_vap, result_basis=AmountBasis.MASS, convert_args=False ) self.vol_mass_phase = pyo.Expression( phlist, rule=rule_vol_mass_phase, doc="Specific volume of phase", ) # molar density def rule_dens_mol_phase(b, p): if p == "Liq": return 1.0 / self.expression_writer.v_liq( **sv_dict_liq, result_basis=AmountBasis.MOLE, convert_args=False ) else: return 1.0 / self.expression_writer.v_vap( **sv_dict_vap, result_basis=AmountBasis.MOLE, convert_args=False ) self.dens_mol_phase = pyo.Expression( phlist, rule=rule_dens_mol_phase, doc="Mole density of phase", ) # specific density def rule_dens_mass_phase(b, p): if p == "Liq": return 1.0 / self.expression_writer.v_liq( **sv_dict_liq, result_basis=AmountBasis.MASS, convert_args=False ) else: return 1.0 / self.expression_writer.v_vap( **sv_dict_vap, result_basis=AmountBasis.MASS, convert_args=False ) self.dens_mass_phase = pyo.Expression( phlist, rule=rule_dens_mass_phase, doc="Mass density of phase", ) # Component flow (for units that need it) def component_flow_mol(b, i): return self.flow_mol self.flow_mol_comp = pyo.Expression( component_list, rule=component_flow_mol, doc="Total mole flow (both phases) of component", ) def component_flow_mass(b, i): return self.flow_mass self.flow_mass_comp = pyo.Expression( component_list, rule=component_flow_mass, doc="Total mass flow (both phases) of component", ) # # Total (mixed phase) properties # # Enthalpy and pressure state variables two-phase properties if self.state_vars == StateVars.PH: if self.amount_basis == AmountBasis.MOLE: self.enth_mass = pyo.Expression( expr=self.enth_mol * params.uc["J/mol to J/kg"] ) else: self.enth_mol = pyo.Expression( expr=self.enth_mass * params.uc["J/kg to J/mol"] ) self.entr_mass = pyo.Expression( expr=self.s_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/kg/K"] ) self.entr_mol = pyo.Expression( expr=self.s_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/mol/K"] ) self.energy_internal_mass = pyo.Expression( expr=self.u_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg to J/kg"] ) self.energy_internal_mol = pyo.Expression( expr=self.u_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg to J/mol"] ) self.cp_mass = pyo.Expression( expr=self.cp_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/kg/K"] ) self.cp_mol = pyo.Expression( expr=self.cp_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/mol/K"] ) self.cv_mass = pyo.Expression( expr=self.cv_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/kg/K"] ) self.cv_mol = pyo.Expression( expr=self.cv_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/mol/K"] ) self.dens_mass = pyo.Expression( expr=1.0 / self.v_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir) ) self.dens_mol = pyo.Expression( expr=params.uc["kg/m3 to mol/m3"] / self.v_hp_func(cmp, self.h_kJ_per_kg, self.p_kPa, _data_dir) ) # Entropy is a state variable elif self.state_vars == StateVars.PS: if self.amount_basis == AmountBasis.MOLE: self.entr_mass = pyo.Expression( expr=self.entr_mol * params.uc["J/mol/K to J/kg/K"] ) else: self.enth_mol = pyo.Expression( expr=self.entr_mass * params.uc["J/kg/K to J/mol/K"] ) self.enth_mass = pyo.Expression( expr=self.h_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir) * params.uc["kJ/kg to J/kg"] ) self.enth_mol = pyo.Expression( expr=self.h_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir) * params.uc["kJ/kg to J/mol"] ) self.energy_internal_mass = pyo.Expression( expr=self.u_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir) * params.uc["kJ/kg to J/kg"] ) self.energy_internal_mol = pyo.Expression( expr=self.u_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir) * params.uc["kJ/kg to J/mol"] ) self.cp_mass = pyo.Expression( expr=self.cp_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/kg/K"] ) self.cp_mol = pyo.Expression( expr=self.cp_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/mol/K"] ) self.cv_mass = pyo.Expression( expr=self.cv_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/kg/K"] ) self.cv_mol = pyo.Expression( expr=self.cv_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/mol/K"] ) self.dens_mass = pyo.Expression( expr=1.0 / self.v_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir) ) self.dens_mol = pyo.Expression( expr=params.uc["kg/m3 to mol/m3"] / self.v_sp_func(cmp, self.s_kJ_per_kgK, self.p_kPa, _data_dir) ) # Internal energy is a state variable elif self.state_vars == StateVars.PU: if self.amount_basis == AmountBasis.MOLE: self.energy_internal_mass = pyo.Expression( expr=self.energy_internal_mol * params.uc["J/mol to J/kg"] ) else: self.energy_internal_mol = pyo.Expression( expr=self.energy_internal_mass * params.uc["J/kg to J/mol"] ) self.enth_mass = pyo.Expression( expr=self.h_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg to J/kg"] ) self.enth_mol = pyo.Expression( expr=self.h_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg to J/mol"] ) self.entr_mass = pyo.Expression( expr=self.s_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/kg/K"] ) self.entr_mol = pyo.Expression( expr=self.s_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/mol/K"] ) self.cp_mass = pyo.Expression( expr=self.cp_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/kg/K"] ) self.cp_mol = pyo.Expression( expr=self.cp_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/mol/K"] ) self.cv_mass = pyo.Expression( expr=self.cv_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/kg/K"] ) self.cv_mol = pyo.Expression( expr=self.cv_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir) * params.uc["kJ/kg/K to J/mol/K"] ) self.dens_mass = pyo.Expression( expr=1.0 / self.v_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir) ) self.dens_mol = pyo.Expression( expr=params.uc["kg/m3 to mol/m3"] / self.v_up_func(cmp, self.u_kJ_per_kg, self.p_kPa, _data_dir) ) else: # T, P, x # enthalpy self.enth_mol = pyo.Expression( expr=sum(self.phase_frac[p] * self.enth_mol_phase[p] for p in phlist) ) self.enth_mass = pyo.Expression( expr=sum(self.phase_frac[p] * self.enth_mass_phase[p] for p in phlist) ) # Entropy self.entr_mol = pyo.Expression( expr=sum(self.phase_frac[p] * self.entr_mol_phase[p] for p in phlist) ) self.entr_mass = pyo.Expression( expr=sum(self.phase_frac[p] * self.entr_mass_phase[p] for p in phlist) ) # Internal Energy self.energy_internal_mol = pyo.Expression( expr=sum( self.phase_frac[p] * self.energy_internal_mol_phase[p] for p in phlist ) ) self.energy_internal_mass = pyo.Expression( expr=sum( self.phase_frac[p] * self.energy_internal_mass_phase[p] for p in phlist ) ) # cp self.cp_mol = pyo.Expression( expr=sum(self.phase_frac[p] * self.cp_mol_phase[p] for p in phlist) ) self.cp_mass = pyo.Expression( expr=sum(self.phase_frac[p] * self.cp_mass_phase[p] for p in phlist) ) # cv self.cv_mol = pyo.Expression( expr=sum(self.phase_frac[p] * self.cv_mol_phase[p] for p in phlist) ) self.cv_mass = pyo.Expression( expr=sum(self.phase_frac[p] * self.cv_mass_phase[p] for p in phlist) ) # mass density self.dens_mass = pyo.Expression( expr=1.0 / sum( self.phase_frac[p] * 1.0 / self.dens_mass_phase[p] for p in phlist ) ) # mole density self.dens_mol = pyo.Expression( expr=1.0 / sum(self.phase_frac[p] * 1.0 / self.dens_mol_phase[p] for p in phlist) ) # heat capacity ratio self.heat_capacity_ratio = pyo.Expression(expr=self.cp_mol / self.cv_mol) # Volumetric flow self.flow_vol = pyo.Expression( expr=self.flow_mol / self.dens_mol, doc="Total liquid + vapor volumetric flow", ) def rule_mole_frac_phase_comp(b, p, j): if p == "Mix": return 1.0 else: return self.phase_frac[p] self.mole_frac_phase_comp = pyo.Expression( pub_phlist, component_list, rule=rule_mole_frac_phase_comp ) self.mass_frac_phase_comp = pyo.Expression( # since pure comp same as mole pub_phlist, component_list, rule=rule_mole_frac_phase_comp ) if viscosity_available(cmp): def rule_visc_d_phase(b, p): if p == "Liq": return self.expression_writer.viscosity_liq( **sv_dict_liq, convert_args=False ) else: return self.expression_writer.viscosity_vap( **sv_dict_vap, convert_args=False ) self.visc_d_phase = pyo.Expression( phlist, rule=rule_visc_d_phase, doc="(Dynamic) viscosity of phase", ) def rule_visc_k_phase(b, p): if p == "Liq": return ( self.expression_writer.viscosity_liq( **sv_dict_liq, convert_args=False ) / self.dens_mass_phase[p] ) else: return ( self.expression_writer.viscosity_vap( **sv_dict_vap, convert_args=False ) / self.dens_mass_phase[p] ) self.visc_k_phase = pyo.Expression( phlist, rule=rule_visc_k_phase, doc="Kinematic viscosity of phase", ) if thermal_conductivity_available(cmp): def rule_therm_cond_phase(b, p): if p == "Liq": return self.expression_writer.thermal_conductivity_liq( **sv_dict_liq, convert_args=False ) else: return self.expression_writer.thermal_conductivity_vap( **sv_dict_vap, convert_args=False ) self.therm_cond_phase = pyo.Expression( phlist, rule=rule_therm_cond_phase, doc="Thermal conductivity of phase", ) if surface_tension_available(cmp): self.surf_tens = pyo.Expression( phlist, expr=self.expression_writer.surface_tension( **sv_dict_liq, convert_args=False ), doc="Thermal conductivity of phase", ) # Define some expressions for the balance terms returned by functions # This is just to allow assigning scale factors to the expressions # returned # # Material flow term expressions if self.amount_basis == AmountBasis.MOLE: def rule_material_flow_terms(b, p): if p == "Mix": return self.flow_mol else: return self.flow_mol * self.phase_frac[p] else: def rule_material_flow_terms(b, p): if p == "Mix": return self.flow_mass else: return self.flow_mass * self.phase_frac[p] self.material_flow_terms = pyo.Expression( pub_phlist, rule=rule_material_flow_terms ) # Enthalpy flow term expressions if self.amount_basis == AmountBasis.MOLE: def rule_enthalpy_flow_terms(b, p): if p == "Mix": return self.enth_mol * self.flow_mol else: return self.enth_mol_phase[p] * self.phase_frac[p] * self.flow_mol else: def rule_enthalpy_flow_terms(b, p): if p == "Mix": return self.enth_mass * self.flow_mass else: return self.enth_mass_phase[p] * self.phase_frac[p] * self.flow_mass self.enthalpy_flow_terms = pyo.Expression( pub_phlist, rule=rule_enthalpy_flow_terms ) # Energy density term expressions if self.amount_basis == AmountBasis.MOLE: def rule_energy_density_terms(b, p): if p == "Mix": return self.dens_mol * self.energy_internal_mol else: return self.dens_mol_phase[p] * self.energy_internal_mol_phase[p] else: def rule_energy_density_terms(b, p): if p == "Mix": return self.dens_mass * self.energy_internal_mass else: return self.dens_mass_phase[p] * self.energy_internal_mass_phase[p] self.energy_density_terms = pyo.Expression( pub_phlist, rule=rule_energy_density_terms )
[docs] def get_material_flow_terms(self, p, j): """Get material flow terms for phase Args: p (str): phase Returns: Expression """ return self.material_flow_terms[p]
[docs] def get_enthalpy_flow_terms(self, p): """Get enthalpy flow terms for phase Args: p (str): phase Returns: Expression """ return self.enthalpy_flow_terms[p]
[docs] def get_material_density_terms(self, p, j): """Get material density terms for phase Args: p (str): phase Returns: Expression """ if self.amount_basis == AmountBasis.MOLE: if p == "Mix": return self.dens_mol else: return self.dens_mol_phase[p] else: if p == "Mix": return self.dens_mass else: return self.dens_mass_phase[p]
[docs] def get_energy_density_terms(self, p): """Get energy density terms for phase Args: p (str): phase Returns: Expression """ return self.energy_density_terms[p]
[docs] def default_material_balance_type(self): """Get default material balance type suggestion""" return MaterialBalanceType.componentTotal
[docs] def default_energy_balance_type(self): """Get default energy balance type suggestion""" return EnergyBalanceType.enthalpyTotal
[docs] def get_material_flow_basis(b): return MaterialFlowBasis.molar
[docs] def define_state_vars(self): return self._state_vars_dict
[docs] def define_display_vars(self): if self.amount_basis == AmountBasis.MOLE: return { "Molar Flow": self.flow_mol, "Mass Flow": self.flow_mass, "T": self.temperature, "P": self.pressure, "Vapor Fraction": self.vapor_frac, "Molar Enthalpy": self.enth_mol, } else: return { "Molar Flow": self.flow_mol, "Mass Flow": self.flow_mass, "T": self.temperature, "P": self.pressure, "Vapor Fraction": self.vapor_frac, "Mass Enthalpy": self.enth_mass, }
[docs] def extensive_state_vars(self): """Return the set of extensive variables""" return self.extensive_set
[docs] def intensive_state_vars(self): """Return the set of intensive variables""" return self.intensive_set
[docs] def model_check(self): """Currently doesn't do anything, here for compatibility"""
def calculate_scaling_factors(self): super().calculate_scaling_factors() if self.params.config.amount_basis == AmountBasis.MOLE: sf_flow = iscale.get_scaling_factor(self.flow_mol, default=1) sf_enth = iscale.get_scaling_factor(self.enth_mol, default=1) sf_inte = iscale.get_scaling_factor(self.energy_internal_mol, default=1) sf_dens = iscale.get_scaling_factor(self.dens_mol, default=1) else: sf_flow = iscale.get_scaling_factor(self.flow_mass, default=1) sf_enth = iscale.get_scaling_factor(self.enth_mass, default=1) sf_inte = iscale.get_scaling_factor(self.energy_internal_mass, default=1) sf_dens = iscale.get_scaling_factor(self.dens_mass, default=1) sf_pres = iscale.get_scaling_factor(self.pressure, default=1) for v in self.material_flow_terms.values(): iscale.set_scaling_factor(v, sf_flow) for v in self.enthalpy_flow_terms.values(): iscale.set_scaling_factor(v, sf_enth * sf_flow) for k, v in self.energy_density_terms.items(): if k == "Mix": iscale.set_scaling_factor(v, sf_inte * sf_dens) else: if self.params.config.amount_basis == AmountBasis.MOLE: sf_inte_p = iscale.get_scaling_factor( self.energy_internal_mol_phase[k], default=1 ) sf_dens_p = iscale.get_scaling_factor( self.dens_mol_phase[k], default=1 ) else: sf_inte_p = iscale.get_scaling_factor( self.energy_internal_mass_phase[k], default=1 ) sf_dens_p = iscale.get_scaling_factor( self.dens_mass_phase[k], default=1 ) iscale.set_scaling_factor(v, sf_inte_p * sf_dens_p) try: iscale.set_scaling_factor(self.eq_sat, sf_pres / 1000.0) except AttributeError: pass # may not have eq_sat, and that's ok try: iscale.set_scaling_factor(self.eq_complementarity, sf_pres / 10) except AttributeError: pass # may not have eq_complementarity which is fine