Coverage for backend/ahuora-builder/src/ahuora_builder/custom/inverted.py: 97%

52 statements  

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

1from pyomo.environ import Var, Constraint, Block, Reference 

2 

3def add_inverted(block: Block,component_name: str): 

4 """ 

5 Adds inverted variables and constraints to a block with variable component_name. 

6 e.g if component_name is 'deltaP', adds deltaP_inverted variable, so we can do  

7 pressure drop instead of pressure increase. 

8 """ 

9 inverted_component_name = f"{component_name}_inverted" 

10 

11 component = getattr(block, component_name) 

12 

13 inverted_component = Var( 

14 component.index_set(), 

15 # Explanation for units: 

16 # Reference() turns a var/expression into a indexed object, even if it's scalar. 

17 # next() gets the first item from the indexed object 

18 # This is a way of getting around the fact that an indexed component 

19 # will only have its units defined at the individual index level. 

20 units=next(Reference(component).values()).get_units(), 

21 ) 

22 

23 block.add_component(inverted_component_name, inverted_component) 

24 

25 def _inverted_rule(b, *indexes): 

26 return inverted_component[indexes] == -component[indexes] 

27 

28 constraint = Constraint( 

29 component.index_set(), 

30 rule=_inverted_rule, 

31 doc=f"Inverted {component_name} Constraint", 

32 ) 

33 block.add_component(f"{inverted_component_name}_constraint", constraint) 

34 

35def initialise_inverted(block: Block, component_name: str): 

36 """ 

37 Initialises the inverted deltaP variables to match the deltaP variable values, 

38 or vice versa (depending on which is fixed). 

39 

40 This is generalised so you can pass any component name. 

41 """ 

42 inverted_component_name = f"{component_name}_inverted" 

43 component = getattr(block, component_name) 

44 inverted_component = getattr(block, inverted_component_name) 

45 

46 for indexes in component.index_set(): 

47 if component[indexes].fixed: 

48 # we can savely assume that the inverted variable is not 

49 # fixed, otherwise this would be an overconstrained model. 

50 inverted_component[indexes].value = -component[indexes].value 

51 elif inverted_component[indexes].fixed: 

52 component[indexes].value = -inverted_component[indexes].value 

53 else: 

54 # neither variable is fixed, 

55 # so let's just leave them as is. 

56 # Hopefully the solver can figure it out. 

57 pass 

58 

59def disable_inverted(block: Block, component_name: str) -> list: 

60 """ 

61 Prepare a native IDAES component for initialization. 

62 

63 When the user-facing inverted variable is fixed, temporarily fix the 

64 native component to the equivalent value before disconnecting their 

65 equality. This prevents the native component and dependent state variables 

66 from drifting while the base IDAES initializer is running. 

67 

68 Returns the indexes of native variables fixed temporarily by this helper. 

69 """ 

70 inverted_component_name = f"{component_name}_inverted" 

71 constraint_name = f"{inverted_component_name}_constraint" 

72 

73 component = getattr(block, component_name) 

74 inverted_component = getattr(block, inverted_component_name) 

75 inverted_constraint = getattr(block, constraint_name) 

76 temporarily_fixed = [] 

77 

78 initialise_inverted(block, component_name) 

79 for indexes in component.index_set(): 

80 if inverted_component[indexes].fixed and not component[indexes].fixed: 

81 component[indexes].fix(-inverted_component[indexes].value) 

82 temporarily_fixed.append(indexes) 

83 

84 inverted_constraint.deactivate() 

85 return temporarily_fixed 

86 

87 

88def enable_inverted( 

89 block: Block, component_name: str, temporarily_fixed: list | None = None 

90): 

91 """ 

92 Restore a disabled inverted equality after native initialization. 

93 

94 Values are synchronized before the equality is activated, and native 

95 variables fixed only for initialization are returned to their original 

96 unfixed state. 

97 """ 

98 inverted_component_name = f"{component_name}_inverted" 

99 constraint_name = f"{inverted_component_name}_constraint" 

100 

101 component = getattr(block, component_name) 

102 inverted_component = getattr(block, inverted_component_name) 

103 inverted_constraint = getattr(block, constraint_name) 

104 

105 temporarily_fixed = set(temporarily_fixed or []) 

106 for indexes in component.index_set(): 

107 if indexes in temporarily_fixed: 

108 component[indexes].set_value(-inverted_component[indexes].value) 

109 elif component[indexes].fixed: 

110 inverted_component[indexes].set_value(-component[indexes].value) 

111 elif inverted_component[indexes].fixed: 111 ↛ 112line 111 didn't jump to line 112 because the condition on line 111 was never true

112 component[indexes].set_value(-inverted_component[indexes].value) 

113 else: 

114 inverted_component[indexes].set_value(-component[indexes].value) 

115 

116 for indexes in temporarily_fixed: 

117 component[indexes].unfix() 

118 

119 inverted_constraint.activate()