Coverage for backend/ahuora-compounds/ahuora_property_packages/helmholtz/helmholtz_extended.py: 38%

35 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-07-22 05:22 +0000

1from pyomo.environ import ( 

2 Expression, 

3 units, 

4 Constraint, 

5 Var 

6) 

7from idaes.models.properties.general_helmholtz.helmholtz_state import HelmholtzStateBlockData, _StateBlock 

8from idaes.models.properties.general_helmholtz.helmholtz_functions import HelmholtzParameterBlockData 

9from idaes.core import declare_process_block_class 

10 

11from ahuora_property_packages.base.flexible_state_block import FlexibleStateBlockData, FlexibleStateBlock 

12 

13 

14class _ExtendedStateBlock(FlexibleStateBlock,_StateBlock): 

15 pass 

16 

17@declare_process_block_class("HelmholtzExtendedStateBlock", block_class=_ExtendedStateBlock) 

18class HelmholtzExtendedStateBlockData(HelmholtzStateBlockData, FlexibleStateBlockData): 

19 

20 def build(blk, *args): 

21 HelmholtzStateBlockData.build(blk, *args) 

22 FlexibleStateBlockData.build(blk, *args) 

23 

24 def constrain_component(blk, component: Var | Expression, value: float) -> Var | None: 

25 """Constrain a Helmholtz property using thermodynamic vapour quality.""" 

26 

27 if component.local_name.startswith("mole_frac_comp"): 

28 # Helmholtz packages are pure-component, so composition is invariant. 

29 return None 

30 

31 if component.local_name == "vapor_frac": 

32 # The native P-H vapour-fraction expression is clipped to zero 

33 # throughout the liquid region and one throughout the vapour region. 

34 # Such endpoint constraints cannot define saturation or determine 

35 # pressure from temperature, so constrain thermodynamic quality using 

36 # the saturated liquid and vapour enthalpies instead. 

37 blk.add_component("vapor_frac_var", Var(units=units.dimensionless)) 

38 blk.add_component( 

39 "vapor_frac_constraint", 

40 Constraint( 

41 expr=blk.enth_mol 

42 == blk.enth_mol_sat_phase["Liq"] 

43 + blk.vapor_frac_var 

44 * ( 

45 blk.enth_mol_sat_phase["Vap"] 

46 - blk.enth_mol_sat_phase["Liq"] 

47 ) 

48 ), 

49 ) 

50 blk.vapor_frac_var.fix(value) 

51 blk.__dict__["vars_to_deactivate"].append(blk.vapor_frac_var) 

52 return blk.vapor_frac_var 

53 

54 return super().constrain_component(component, value) 

55 

56 def add_extra_expressions(blk): 

57 super().add_extra_expressions() 

58 

59 # Generic property packages support temperature_bubble and temperature_dew, 

60 # Helmholtz is pure so these are the same, and so it only has 

61 # temperature_sat. https://idaes-pse.readthedocs.io/en/stable/reference_guides/model_libraries/generic/property_models/helmholtz.html#expressions 

62 # We add them here so that the property package interface is consistent. 

63 blk.add_component("temperature_bubble", Expression(expr=blk.temperature_sat)) 

64 blk.add_component("temperature_dew", Expression(expr=blk.temperature_sat)) 

65 

66 # For numerical stability when constraining temperature, we actually smooth out the temperature equilibrium curve.  

67 # Otherwise, the solver has difficulty solving through the vapor fraction region as there is zero gradient. 

68 

69 # rename temperature to old_temperature 

70 old_temperature = blk.temperature 

71 blk.del_component("temperature") 

72 blk.add_component("old_temperature", old_temperature) 

73 # create smooth temperature expression 

74 blk.add_component("temperature", Expression( 

75 expr=blk.old_temperature + (blk.enth_mol / (1 * units.J/units.mol))*0.000001 * units.K 

76 )) 

77 

78 

79@declare_process_block_class("HelmholtzExtendedParameterBlock") 

80class HelmholtzExtendedParameterBlockData(HelmholtzParameterBlockData): 

81 def build(self): 

82 super().build() 

83 self._state_block_class = HelmholtzExtendedStateBlock # noqa: F821