def dens_mol_phase(b, p): pobj = b.params.get_phase(p) if pobj.is_vapor_phase() or pobj.is_liquid_phase(): return b.pressure / (Cubic.gas_constant(b) * b.temperature * b.compress_fact_phase[p]) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def enth_mol_phase(blk, p): pobj = blk.params.get_phase(p) if not (pobj.is_vapor_phase() or pobj.is_liquid_phase()): raise PropertyNotSupportedError(_invalid_phase_msg(blk.name, p)) cname = pobj._cubic_type.name am = getattr(blk, cname + "_am")[p] bm = getattr(blk, cname + "_bm")[p] B = getattr(blk, cname + "_B")[p] dadT = getattr(blk, cname + "_dadT")[p] Z = blk.compress_fact_phase[p] EoS_u = EoS_param[pobj._cubic_type]['u'] EoS_w = EoS_param[pobj._cubic_type]['w'] EoS_p = sqrt(EoS_u**2 - 4 * EoS_w) # Derived from equation on pg. 120 in Properties of Gases and Liquids return (((blk.temperature * dadT - am) * safe_log( (2 * Z + B * (EoS_u + EoS_p)) / (2 * Z + B * (EoS_u - EoS_p)), eps=1e-6) + Cubic.gas_constant(blk) * blk.temperature * (Z - 1) * bm * EoS_p) / (bm * EoS_p) + sum(blk.mole_frac_phase_comp[p, j] * get_method(blk, "enth_mol_ig_comp", j) (blk, cobj(blk, j), blk.temperature) for j in blk.components_in_phase(p)))
def entr_mol_phase_comp(b, p, j): if p == "Vap": return get_method(b, "entr_mol_ig_comp")(b, j, b.temperature) elif p == "Liq": return get_method(b, "entr_mol_liq_comp")(b, j, b.temperature) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def fug_phase_comp_eq(b, p, j, pp): pobj = b.params.get_phase(p) if pobj.is_vapor_phase() or pobj.is_liquid_phase(): return (b.mole_frac_phase_comp[p, j] * b.pressure * exp(_log_fug_coeff_phase_comp_eq(b, p, j, pp))) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def entr_mol_phase_comp(blk, p, j): pobj = blk.params.get_phase(p) if not (pobj.is_vapor_phase() or pobj.is_liquid_phase()): raise PropertyNotSupportedError(_invalid_phase_msg(blk.name, p)) cname = pobj._cubic_type.name bm = getattr(blk, cname + "_bm")[p] B = getattr(blk, cname + "_B")[p] dadT = getattr(blk, cname + "_dadT")[p] Z = blk.compress_fact_phase[p] EoS_u = EoS_param[pobj._cubic_type]['u'] EoS_w = EoS_param[pobj._cubic_type]['w'] EoS_p = sqrt(EoS_u**2 - 4 * EoS_w) # See pg. 102 in Properties of Gases and Liquids return (((Cubic.gas_constant(blk) * safe_log( (Z - B) / Z, eps=1e-6) * bm * EoS_p + Cubic.gas_constant(blk) * safe_log(Z * blk.params.pressure_ref / (blk.mole_frac_phase_comp[p, j] * blk.pressure), eps=1e-6) * bm * EoS_p + dadT * safe_log( (2 * Z + B * (EoS_u + EoS_p)) / (2 * Z + B * (EoS_u - EoS_p)), eps=1e-6)) / (bm * EoS_p)) + get_method(blk, "entr_mol_ig_comp", j)(blk, cobj(blk, j), blk.temperature))
def rule_boilup_ratio(self, t): if hasattr(self.control_volume.properties_out[t], "flow_mol_phase"): return self.boilup_ratio * \ sum(self.control_volume.properties_out[t]. flow_mol_phase[p] for p in self._liquid_set) == \ sum(self.control_volume. properties_out[t].flow_mol_phase["Vap"] for p in self._vapor_set) elif hasattr(self.control_volume.properties_out[t], "flow_mol_phase_comp"): return self.boilup_ratio * \ sum(self.control_volume.properties_out[t]. flow_mol_phase_comp[p, i] for p in self._liquid_set for i in self.control_volume.properties_out[t]. params.component_list) == \ sum(self.control_volume.properties_out[t]. flow_mol_phase_comp[p, i] for p in self._vapor_set for i in self.control_volume.properties_out[t]. params.component_list) else: raise PropertyNotSupportedError( "Unrecognized names for flow variables encountered " "while building the constraint for reboiler.")
def cv_mol_phase_comp(b, p, j): pobj = b.params.get_phase(p) if pobj.is_vapor_phase(): return EoSBase.cv_mol_ig_comp_pure(b, j) elif pobj.is_liquid_phase(): return EoSBase.cv_mol_ls_comp_pure(b, j) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def fug_phase_comp(b, p, j): if p == "Vap": return b.mole_frac_phase_comp[p, j]*b.pressure elif p == "Liq": return b.mole_frac_phase_comp[p, j] * \ get_method(b, "pressure_sat_comp")(b, j, b._teq) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def _rxn_rate_conv(b, t, x, j, has_rate_reactions): """ Method to determine conversion term for reaction rate terms in material balance equations. This method gets the basis of the material flow and reaction rate terms and determines the correct conversion factor. """ # If rate reactions are not required, skip the rest and return 1 if not has_rate_reactions: return 1 if x is None: # 0D control volume flow_basis = b.properties_out[t].get_material_flow_basis() prop = b.properties_out[t] rxn_basis = b.reactions[t].get_reaction_rate_basis() else: # 1D control volume flow_basis = b.properties[t, x].get_material_flow_basis() prop = b.properties[t, x] rxn_basis = b.reactions[t, x].get_reaction_rate_basis() # Check for undefined basis if flow_basis == MaterialFlowBasis.other: raise ConfigurationError( "{} contains reaction terms, but the property package " "used an undefined basis (MaterialFlowBasis.other). " "Rate based reaction terms require the property " "package to define the basis of the material flow " "terms.".format(b.name)) if rxn_basis == MaterialFlowBasis.other: raise ConfigurationError( "{} contains reaction terms, but the reaction package " "used an undefined basis (MaterialFlowBasis.other). " "Rate based reaction terms require the reaction " "package to define the basis of the reaction rate " "terms.".format(b.name)) try: if flow_basis == rxn_basis: return 1 elif (flow_basis == MaterialFlowBasis.mass and rxn_basis == MaterialFlowBasis.molar): return prop.mw[j] elif (flow_basis == MaterialFlowBasis.molar and rxn_basis == MaterialFlowBasis.mass): return 1 / prop.mw[j] else: raise BurntToast( "{} encountered unrecognsied combination of bases " "for reaction rate terms. Please contact the IDAES" " developers with this bug.".format(b.name)) except AttributeError: raise PropertyNotSupportedError( "{} property package does not support " "molecular weight (mw), which is required for " "using property and reaction packages with " "different bases.".format(b.name))
def dens_mol_phase(b, p): if p == "Vap": return b.pressure/(const.gas_constant*b.temperature) elif p == "Liq": return sum(b.mole_frac_phase_comp[p, j] * get_method(b, "dens_mol_liq_comp")(b, j, b.temperature) for j in b.components_in_phase(p)) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def _fug_phase_comp(b, p, j, T): pobj = b.params.get_phase(p) if pobj.is_vapor_phase(): return b.mole_frac_phase_comp[p, j] * b.pressure elif pobj.is_liquid_phase(): return (b.mole_frac_phase_comp[p, j] * get_method(b, "pressure_sat_comp", j)(b, cobj(b, j), T)) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def log_fug_phase_comp_eq(b, p, j, pp): pobj = b.params.get_phase(p) if pobj.is_vapor_phase(): return log(b.mole_frac_phase_comp[p, j]) + log(b.pressure) elif pobj.is_liquid_phase(): return (log(b.mole_frac_phase_comp[p, j]) + log(get_method(b, "pressure_sat_comp", j)( b, cobj(b, j), b.temperature))) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def enth_mol_phase_comp(b, p, j): pobj = b.params.get_phase(p) if pobj.is_vapor_phase(): return get_method(b, "enth_mol_ig_comp", j)( b, cobj(b, j), b.temperature) elif pobj.is_liquid_phase(): return get_method(b, "enth_mol_liq_comp", j)( b, cobj(b, j), b.temperature) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def dens_mol_phase(b, p): pobj = b.params.get_phase(p) if pobj.is_vapor_phase(): return b.pressure/(Ideal.gas_constant(b)*b.temperature) elif pobj.is_liquid_phase(): return sum(b.mole_frac_phase_comp[p, j] * get_method(b, "dens_mol_liq_comp", j)( b, cobj(b, j), b.temperature) for j in b.components_in_phase(p)) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def entr_mol_phase_comp(b, p, j): pobj = b.params.get_phase(p) if pobj.is_vapor_phase(): return (get_method(b, "entr_mol_ig_comp", j)(b, cobj( b, j), b.temperature) - Ideal.gas_constant(b) * log(b.mole_frac_phase_comp[p, j] * b.pressure / b.params.pressure_ref)) elif pobj.is_liquid_phase(): # Assume no pressure/volume dependecy of entropy for ideal liquids return (get_method(b, "entr_mol_liq_comp", j)(b, cobj(b, j), b.temperature)) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def compress_fact_phase(b, p): pobj = b.params.get_phase(p) cname = pobj._cubic_type.name A = getattr(b, cname + "_A") B = getattr(b, cname + "_B") f = getattr(b, "_" + cname + "_ext_func_param") if pobj.is_vapor_phase(): proc = getattr(b, "_" + cname + "_proc_Z_vap") elif pobj.is_liquid_phase(): proc = getattr(b, "_" + cname + "_proc_Z_liq") else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p)) return proc(f, A[p], B[p])
def log_fug_phase_comp_Pbub(b, p, j, pp): pobj = b.params.get_phase(p) cobj = b.params.get_component(j) if pobj.is_vapor_phase(): return (log(b._mole_frac_pbub[pp[0], pp[1], j]) + log(b.pressure_bubble[pp])) elif pobj.is_liquid_phase(): if (cobj.config.henry_component is not None and p in cobj.config.henry_component): return (log(b.mole_frac_comp[j]) + log(b.henry[p, j])) else: return (log(b.mole_frac_comp[j]) + log(b.pressure_sat_comp[j])) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def fug_coeff_phase_comp(blk, p, j): pobj = blk.params.get_phase(p) ctype = pobj._cubic_type if not (pobj.is_vapor_phase() or pobj.is_liquid_phase()): raise PropertyNotSupportedError(_invalid_phase_msg(blk.name, p)) cname = pobj._cubic_type.name b = getattr(blk, cname + "_b")[j] bm = getattr(blk, cname + "_bm")[p] A = getattr(blk, cname + "_A")[p] B = getattr(blk, cname + "_B")[p] delta = getattr(blk, cname + "_delta")[p, j] Z = blk.compress_fact_phase[p] return exp(_log_fug_coeff_method(A, b, bm, B, delta, Z, ctype))
def log_fug_phase_comp_Tdew(b, p, j, pp): pobj = b.params.get_phase(p) cobj = b.params.get_component(j) if pobj.is_vapor_phase(): return log(b.mole_frac_comp[j]) + log(b.pressure) elif pobj.is_liquid_phase(): if (cobj.config.henry_component is not None and p in cobj.config.henry_component): return (log(b._mole_frac_tdew[pp[0], pp[1], j]) + log(get_method(b, "henry_component", j, p)( b, p, j, b.temperature_dew[pp]))) else: return (log(b._mole_frac_tdew[pp[0], pp[1], j]) + log(get_method(b, "pressure_sat_comp", j)( b, cobj, b.temperature_dew[pp]))) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def _fug_phase_comp(b, p, j, T): pobj = b.params.get_phase(p) if pobj.is_vapor_phase(): return b.get_mole_frac(p)[p, j] * b.pressure elif pobj.is_liquid_phase(): if (cobj(b, j).config.henry_component is not None and p in cobj(b, j).config.henry_component): # Use Henry's Law return henry_pressure(b, p, j, T) elif cobj(b, j).config.has_vapor_pressure: # Use Raoult's Law return (b.get_mole_frac(p)[p, j] * get_method(b, "pressure_sat_comp", j)(b, cobj(b, j), T)) else: return Expression.Skip else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def vol_mol_phase(b, p): pobj = b.params.get_phase(p) if pobj.is_vapor_phase(): return Ideal.gas_constant(b)*b.temperature/b.pressure elif pobj.is_liquid_phase(): ptype = "liq" elif pobj.is_solid_phase(): ptype = "sol" else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p)) v_expr = 0 for j in b.components_in_phase(p): # First try to get a method for vol_mol v_comp = Ideal.get_vol_mol_pure(b, ptype, j, b.temperature) v_expr += b.get_mole_frac()[p, j]*v_comp return v_expr
def enth_mol_phase(b, p): pobj = b.params.get_phase(p) if pobj.is_vapor_phase(): return sum(b.get_mole_frac()[p, j]*b.enth_mol_phase_comp[p, j] for j in b.components_in_phase(p)) elif pobj.is_liquid_phase(): return (sum(b.get_mole_frac()[p, j] * get_method(b, "enth_mol_liq_comp", j)( b, cobj(b, j), b.temperature) for j in b.components_in_phase(p)) + (b.pressure-b.params.pressure_ref)/b.dens_mol_phase[p]) elif pobj.is_solid_phase(): return (sum(b.get_mole_frac()[p, j] * get_method(b, "enth_mol_sol_comp", j)( b, cobj(b, j), b.temperature) for j in b.components_in_phase(p)) + (b.pressure-b.params.pressure_ref)/b.dens_mol_phase[p]) else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def log_fug_phase_comp_eq(b, p, j, pp): pobj = b.params.get_phase(p) if pobj.is_vapor_phase(): return log(b.get_mole_frac()[p, j]) + log(b.pressure) elif pobj.is_liquid_phase(): if (cobj(b, j).config.henry_component is not None and p in cobj(b, j).config.henry_component): # Use Henry's Law return log(b.get_mole_frac()[p, j]) + log(b.henry[p, j]) elif cobj(b, j).config.has_vapor_pressure: # Use Raoult's Law return (log(b.get_mole_frac()[p, j]) + log(get_method(b, "pressure_sat_comp", j)( b, cobj(b, j), b.temperature))) else: return Expression.Skip else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def energy_internal_mol_phase_comp(blk, p, j): pobj = blk.params.get_phase(p) if not (pobj.is_vapor_phase() or pobj.is_liquid_phase()): raise PropertyNotSupportedError(_invalid_phase_msg(blk.name, p)) cname = pobj._cubic_type.name am = getattr(blk, cname + "_am")[p] bm = getattr(blk, cname + "_bm")[p] B = getattr(blk, cname + "_B")[p] dadT = getattr(blk, cname + "_dadT")[p] Z = blk.compress_fact_phase[p] EoS_u = EoS_param[pobj._cubic_type]['u'] EoS_w = EoS_param[pobj._cubic_type]['w'] EoS_p = sqrt(EoS_u**2 - 4 * EoS_w) # Derived from equation on pg. 120 in Properties of Gases and Liquids # Departure function for U is similar to H minus the RT(Z-1) term return (((blk.temperature * dadT - am) * safe_log( (2 * Z + B * (EoS_u + EoS_p)) / (2 * Z + B * (EoS_u - EoS_p)), eps=1e-6)) / (bm * EoS_p) + EoSBase.energy_internal_mol_ig_comp_pure(blk, j))
def _log_fug_coeff_phase_comp_eq(blk, p, j, pp): pobj = blk.params.get_phase(p) if not (pobj.is_vapor_phase() or pobj.is_liquid_phase()): raise PropertyNotSupportedError(_invalid_phase_msg(blk.name, p)) cname = pobj._cubic_type.name b = getattr(blk, cname + "_b") bm = getattr(blk, cname + "_bm") Aeq = getattr(blk, "_" + cname + "_A_eq") Beq = getattr(blk, "_" + cname + "_B_eq") delta_eq = getattr(blk, "_" + cname + "_delta_eq") f = getattr(blk, "_" + cname + "_ext_func_param") if pobj.is_vapor_phase(): proc = getattr(blk, "_" + cname + "_proc_Z_vap") elif pobj.is_liquid_phase(): proc = getattr(blk, "_" + cname + "_proc_Z_liq") def Zeq(p): return proc(f, Aeq[pp, p], Beq[pp, p]) return _log_fug_coeff_method(Aeq[pp, p], b[j], bm[p], Beq[pp, p], delta_eq[pp, p, j], Zeq(p), pobj._cubic_type)
def compress_fact_phase(b, p): pobj = b.params.get_phase(p) if pobj.is_vapor_phase() or pobj.is_liquid_phase(): return 1 else: raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p))
def log_fug_coeff_phase_comp_Pdew(b, p, j, pp): pobj = b.params.get_phase(p) if not (pobj.is_vapor_phase() or pobj.is_liquid_phase()): raise PropertyNotSupportedError(_invalid_phase_msg(b.name, p)) return log(1)
def add_material_mixing_equations(self, inlet_blocks, mixed_block, mb_type): """ Add material mixing equations. """ pp = self.config.property_package # Get phase component list(s) pc_set = mixed_block.phase_component_set # Get units metadata units = pp.get_metadata() flow_basis = mixed_block[ self.flowsheet().time.first()].get_material_flow_basis() if flow_basis == MaterialFlowBasis.molar: flow_units = units.get_derived_units("flow_mole") elif flow_basis == MaterialFlowBasis.mass: flow_units = units.get_derived_units("flow_mass") else: # Let this pass for now with no units flow_units = None if mb_type == MaterialBalanceType.componentPhase: # Create equilibrium generation term and constraints if required if self.config.has_phase_equilibrium is True: try: self.phase_equilibrium_generation = Var( self.flowsheet().time, pp.phase_equilibrium_idx, domain=Reals, doc="Amount of generation in unit by phase equilibria", units=flow_units) except AttributeError: raise PropertyNotSupportedError( "{} Property package does not contain a list of phase " "equilibrium reactions (phase_equilibrium_idx), " "thus does not support phase equilibrium.".format( self.name)) # Define terms to use in mixing equation def phase_equilibrium_term(b, t, p, j): if self.config.has_phase_equilibrium: sd = {} for r in pp.phase_equilibrium_idx: if pp.phase_equilibrium_list[r][0] == j: if (pp.phase_equilibrium_list[r][1][0] == p): sd[r] = 1 elif (pp.phase_equilibrium_list[r][1][1] == p): sd[r] = -1 else: sd[r] = 0 else: sd[r] = 0 return sum(b.phase_equilibrium_generation[t, r] * sd[r] for r in pp.phase_equilibrium_idx) else: return 0 # Write phase-component balances @self.Constraint( self.flowsheet().time, pc_set, doc="Material mixing equations", ) def material_mixing_equations(b, t, p, j): return 0 == ( sum(inlet_blocks[i][t].get_material_flow_terms(p, j) for i in range(len(inlet_blocks))) - mixed_block[t].get_material_flow_terms(p, j) + phase_equilibrium_term(b, t, p, j)) elif mb_type == MaterialBalanceType.componentTotal: # Write phase-component balances @self.Constraint( self.flowsheet().time, mixed_block.component_list, doc="Material mixing equations", ) def material_mixing_equations(b, t, j): return 0 == sum( sum(inlet_blocks[i][t].get_material_flow_terms(p, j) for i in range(len(inlet_blocks))) - mixed_block[t].get_material_flow_terms(p, j) for p in mixed_block.phase_list if (p, j) in pc_set) elif mb_type == MaterialBalanceType.total: # Write phase-component balances @self.Constraint(self.flowsheet().time, doc="Material mixing equations") def material_mixing_equations(b, t): return 0 == sum( sum( sum(inlet_blocks[i][t].get_material_flow_terms(p, j) for i in range(len(inlet_blocks))) - mixed_block[t].get_material_flow_terms(p, j) for j in mixed_block.component_list if (p, j) in pc_set) for p in mixed_block.phase_list) elif mb_type == MaterialBalanceType.elementTotal: raise ConfigurationError("{} Mixers do not support elemental " "material balances.".format(self.name)) elif mb_type == MaterialBalanceType.none: pass else: raise BurntToast( "{} Mixer received unrecognised value for " "material_balance_type. This should not happen, " "please report this bug to the IDAES developers.".format( self.name))
def build(self): """ General build method for MixerData. This method calls a number of sub-methods which automate the construction of expected attributes of unit models. Inheriting models should call `super().build`. Args: None Returns: None """ # Call super.build() super(MixerData, self).build() # Call setup methods from ControlVolumeBlockData self._get_property_package() self._get_indexing_sets() # Create list of inlet names inlet_list = self.create_inlet_list() # Build StateBlocks inlet_blocks = self.add_inlet_state_blocks(inlet_list) if self.config.mixed_state_block is None: mixed_block = self.add_mixed_state_block() else: mixed_block = self.get_mixed_state_block() mb_type = self.config.material_balance_type if mb_type == MaterialBalanceType.useDefault: t_ref = self.flowsheet().time.first() mb_type = mixed_block[t_ref].default_material_balance_type() if mb_type != MaterialBalanceType.none: self.add_material_mixing_equations(inlet_blocks=inlet_blocks, mixed_block=mixed_block, mb_type=mb_type) else: raise BurntToast("{} received unrecognised value for " "material_mixing_type argument. This " "should not occur, so please contact " "the IDAES developers with this bug.".format( self.name)) if self.config.energy_mixing_type == MixingType.extensive: self.add_energy_mixing_equations(inlet_blocks=inlet_blocks, mixed_block=mixed_block) elif self.config.energy_mixing_type == MixingType.none: pass else: raise ConfigurationError( "{} received unrecognised value for " "material_mixing_type argument. This " "should not occur, so please contact " "the IDAES developers with this bug.".format(self.name)) # Add to try/expect to catch cases where pressure is not supported # by properties. try: if self.config.momentum_mixing_type == MomentumMixingType.minimize: self.add_pressure_minimization_equations( inlet_blocks=inlet_blocks, mixed_block=mixed_block) elif (self.config.momentum_mixing_type == MomentumMixingType.equality): self.add_pressure_equality_equations(inlet_blocks=inlet_blocks, mixed_block=mixed_block) elif (self.config.momentum_mixing_type == MomentumMixingType.minimize_and_equality): self.add_pressure_minimization_equations( inlet_blocks=inlet_blocks, mixed_block=mixed_block) self.add_pressure_equality_equations(inlet_blocks=inlet_blocks, mixed_block=mixed_block) self.pressure_equality_constraints.deactivate() elif self.config.momentum_mixing_type == MomentumMixingType.none: pass else: raise ConfigurationError( "{} recieved unrecognised value for " "momentum_mixing_type argument. This " "should not occur, so please contact " "the IDAES developers with this bug.".format(self.name)) except PropertyNotSupportedError: raise PropertyNotSupportedError( "{} The property package supplied for this unit does not " "appear to support pressure, which is required for momentum " "mixing. Please set momentum_mixing_type to " "MomentumMixingType.none or provide a property package which " "supports pressure.".format(self.name)) self.add_port_objects(inlet_list, inlet_blocks, mixed_block)
def __getattr__(self, attr): """ This method is used to avoid generating unnecessary property calculations in state blocks. __getattr__ is called whenever a property is called for, and if a propery does not exist, it looks for a method to create the required property, and any associated components. Create a property calculation if needed. Return an attrbute error if attr == 'domain' or starts with a _ . The error for _ prevents a recursion error if trying to get a function to create a property and that function doesn't exist. Pyomo also ocasionally looks for things that start with _ and may not exist. Pyomo also looks for the domain attribute, and it may not exist. This works by creating a property calculation by calling the "_"+attr function. A list of __getattr__ calls is maintained in self.__getattrcalls to check for recursive loops which maybe useful for debugging. This list is cleared after __getattr__ completes successfully. Args: attr: an attribute to create and return. Should be a property component. """ if self._lock_attribute_creation: raise AttributeError( f"{attr} does not exist, and attribute creation is locked.") def clear_call_list(self, attr): """Local method for cleaning up call list when a call is handled. Args: attr: attribute currently being handled """ if self.__getattrcalls[-1] == attr: if len(self.__getattrcalls) <= 1: del self.__getattrcalls else: del self.__getattrcalls[-1] else: raise PropertyPackageError( "{} Trying to remove call {} from __getattr__" " call list, however this is not the most " "recent call in the list ({}). This indicates" " a bug in the __getattr__ calls. Please " "contact the IDAES developers with this bug.".format( self.name, attr, self.__getattrcalls[-1])) # Check that attr is not something we shouldn't touch if attr == "domain" or attr.startswith("_"): # Don't interfere with anything by getting attributes that are # none of my business raise PropertyPackageError( '{} {} does not exist, but is a protected ' 'attribute. Check the naming of your ' 'components to avoid any reserved names'.format( self.name, attr)) if attr == "config": try: self._get_config_args() return self.config except: raise BurntToast("{} getattr method was triggered by a call " "to the config block, but _get_config_args " "failed. This should never happen.") # Check for recursive calls try: # Check if __getattrcalls is initialized self.__getattrcalls except AttributeError: # Initialize it self.__getattrcalls = [attr] else: # Check to see if attr already appears in call list if attr in self.__getattrcalls: # If it does, indicates a recursive loop. if attr == self.__getattrcalls[-1]: # attr method is calling itself self.__getattrcalls.append(attr) raise PropertyPackageError( '{} _{} made a recursive call to ' 'itself, indicating a potential ' 'recursive loop. This is generally ' 'caused by the {} method failing to ' 'create the {} component.'.format( self.name, attr, attr, attr)) else: self.__getattrcalls.append(attr) raise PropertyPackageError( '{} a potential recursive loop has been ' 'detected whilst trying to construct {}. ' 'A method was called, but resulted in a ' 'subsequent call to itself, indicating a ' 'recursive loop. This may be caused by a ' 'method trying to access a component out ' 'of order for some reason (e.g. it is ' 'declared later in the same method). See ' 'the __getattrcalls object for a list of ' 'components called in the __getattr__ ' 'sequence.'.format(self.name, attr)) # If not, add call to list self.__getattrcalls.append(attr) # Get property information from properties metadata try: m = self.config.parameters.get_metadata().properties if m is None: raise PropertyPackageError( '{} property package get_metadata()' ' method returned None when trying to create ' '{}. Please contact the developer of the ' 'property package'.format(self.name, attr)) except KeyError: # If attr not in metadata, assume package does not # support property clear_call_list(self, attr) raise PropertyNotSupportedError( '{} {} is not supported by property package (property is ' 'not listed in package metadata properties).'.format( self.name, attr)) # Get method name from resulting properties try: if m[attr]['method'] is None: # If method is none, property should be constructed # by property package, so raise PropertyPackageError clear_call_list(self, attr) raise PropertyPackageError( '{} {} should be constructed automatically ' 'by property package, but is not present. ' 'This can be caused by methods being called ' 'out of order.'.format(self.name, attr)) elif m[attr]['method'] is False: # If method is False, package does not support property # Raise NotImplementedError clear_call_list(self, attr) raise PropertyNotSupportedError( '{} {} is not supported by property package ' '(property method is listed as False in ' 'package property metadata).'.format(self.name, attr)) elif isinstance(m[attr]['method'], str): # Try to get method name in from PropertyBlock object try: f = getattr(self, m[attr]['method']) except AttributeError: # If fails, method does not exist clear_call_list(self, attr) raise PropertyPackageError( '{} {} package property metadata method ' 'returned a name that does not correspond' ' to any method in the property package. ' 'Please contact the developer of the ' 'property package.'.format(self.name, attr)) else: # Otherwise method name is invalid clear_call_list(self, attr) raise PropertyPackageError( '{} {} package property metadata method ' 'returned invalid value for method name. ' 'Please contact the developer of the ' 'property package.'.format(self.name, attr)) except KeyError: # No method key - raise Exception # Need to use an AttributeError so Pyomo.DAE will handle this clear_call_list(self, attr) raise PropertyNotSupportedError( '{} package property metadata method ' 'does not contain a method for {}. ' 'Please select a package which supports ' 'the necessary properties for your process.'.format( self.name, attr)) # Call attribute if it is callable # If this fails, it should return a meaningful error. if callable(f): try: f() except Exception: # Clear call list and reraise error clear_call_list(self, attr) raise else: # If f is not callable, inform the user and clear call list clear_call_list(self, attr) raise PropertyPackageError( '{} tried calling attribute {} in order to create ' 'component {}. However the method is not callable.'.format( self.name, f, attr)) # Clear call list, and return comp = getattr(self, attr) clear_call_list(self, attr) return comp