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
« prev ^ index » next coverage.py v7.10.7, created at 2026-07-22 05:22 +0000
1from pyomo.environ import Var, Constraint, Block, Reference
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"
11 component = getattr(block, component_name)
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 )
23 block.add_component(inverted_component_name, inverted_component)
25 def _inverted_rule(b, *indexes):
26 return inverted_component[indexes] == -component[indexes]
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)
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).
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)
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
59def disable_inverted(block: Block, component_name: str) -> list:
60 """
61 Prepare a native IDAES component for initialization.
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.
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"
73 component = getattr(block, component_name)
74 inverted_component = getattr(block, inverted_component_name)
75 inverted_constraint = getattr(block, constraint_name)
76 temporarily_fixed = []
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)
84 inverted_constraint.deactivate()
85 return temporarily_fixed
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.
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"
101 component = getattr(block, component_name)
102 inverted_component = getattr(block, inverted_component_name)
103 inverted_constraint = getattr(block, constraint_name)
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)
116 for indexes in temporarily_fixed:
117 component[indexes].unfix()
119 inverted_constraint.activate()