Coverage for backend/ahuora-builder/src/ahuora_builder/model_statistics_optimizations.py: 86%
130 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-13 02:47 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-13 02:47 +0000
1from __future__ import annotations
3from dataclasses import dataclass
4import sys
6from pyomo.common.collections import ComponentSet
7from pyomo.core.expr import identify_variables
9import idaes.core.util.model_statistics as idaes_model_statistics
12@dataclass(frozen=True)
13class DegreesOfFreedomInfo:
14 active_equalities: int
15 unfixed_variables_in_activated_equalities: int
16 degrees_of_freedom: int
19@dataclass(frozen=True)
20class VariableUsageInfo:
21 all_variables: ComponentSet
22 active_constraint_variables: ComponentSet
23 equality_variables: ComponentSet
24 inequality_variables: ComponentSet
27def _collect_unfixed_variables_in_activated_equalities(
28 block,
29) -> tuple[int, ComponentSet]:
30 # Share a named-expression cache across all equalities in the same traversal
31 # so repeated expression fragments are only expanded once.
32 named_expression_cache: dict[int, tuple] = {}
33 var_set = ComponentSet()
34 active_equalities = 0
36 for constraint in idaes_model_statistics.activated_equalities_generator(block):
37 active_equalities += 1
38 for var in identify_variables(
39 constraint.body,
40 include_fixed=False,
41 named_expression_cache=named_expression_cache,
42 ):
43 var_set.add(var)
45 for var in idaes_model_statistics.greybox_variables(block): 45 ↛ 46line 45 didn't jump to line 46 because the loop on line 45 never started
46 if not var.fixed:
47 var_set.add(var)
49 return active_equalities, var_set
52def _collect_variable_usage(block) -> VariableUsageInfo:
53 named_expression_cache: dict[int, tuple] = {}
54 all_variables = ComponentSet()
55 active_constraint_variables = ComponentSet()
56 equality_variables = ComponentSet()
57 inequality_variables = ComponentSet()
59 for var in idaes_model_statistics._iter_indexed_block_data_objects( # pylint: disable=protected-access
60 block, ctype=idaes_model_statistics.Var, active=True, descend_into=True
61 ):
62 all_variables.add(var)
64 for constraint in idaes_model_statistics.activated_constraints_generator(block):
65 is_equality = (
66 constraint.upper is not None
67 and constraint.lower is not None
68 and idaes_model_statistics.value(constraint.upper)
69 == idaes_model_statistics.value(constraint.lower)
70 )
71 if is_equality: 71 ↛ 74line 71 didn't jump to line 74 because the condition on line 71 was always true
72 target_set = equality_variables
73 else:
74 target_set = inequality_variables
76 for var in identify_variables(
77 constraint.body,
78 include_fixed=True,
79 named_expression_cache=named_expression_cache,
80 ):
81 active_constraint_variables.add(var)
82 target_set.add(var)
84 for var in idaes_model_statistics.greybox_variables(block): 84 ↛ 85line 84 didn't jump to line 85 because the loop on line 84 never started
85 all_variables.add(var)
86 active_constraint_variables.add(var)
87 equality_variables.add(var)
89 return VariableUsageInfo(
90 all_variables=all_variables,
91 active_constraint_variables=active_constraint_variables,
92 equality_variables=equality_variables,
93 inequality_variables=inequality_variables,
94 )
97def degrees_of_freedom_info(block) -> DegreesOfFreedomInfo:
98 active_equalities, unfixed_var_set = _collect_unfixed_variables_in_activated_equalities(
99 block
100 )
101 unfixed_variables = len(unfixed_var_set)
102 return DegreesOfFreedomInfo(
103 active_equalities=active_equalities,
104 unfixed_variables_in_activated_equalities=unfixed_variables,
105 degrees_of_freedom=unfixed_variables - active_equalities,
106 )
109def unfixed_variables_in_activated_equalities_set(block):
110 _, unfixed_var_set = _collect_unfixed_variables_in_activated_equalities(block)
111 return unfixed_var_set
114def number_unfixed_variables_in_activated_equalities(block):
115 return degrees_of_freedom_info(block).unfixed_variables_in_activated_equalities
118def degrees_of_freedom(block):
119 return degrees_of_freedom_info(block).degrees_of_freedom
122def report_statistics(block, ostream=None):
123 if ostream is None: 123 ↛ 126line 123 didn't jump to line 126 because the condition on line 123 was always true
124 ostream = sys.stdout
126 variable_usage = _collect_variable_usage(block)
127 all_variables = variable_usage.all_variables
128 fixed_variables = ComponentSet(v for v in all_variables if v.fixed)
129 unused_variables = all_variables - variable_usage.active_constraint_variables
130 fixed_unused_variables = ComponentSet(v for v in unused_variables if v.fixed)
131 variables_only_in_inequalities = (
132 variable_usage.inequality_variables - variable_usage.equality_variables
133 )
134 fixed_variables_only_in_inequalities = ComponentSet(
135 v for v in variables_only_in_inequalities if v.fixed
136 )
137 dof_info = degrees_of_freedom_info(block)
139 tab = " " * 4
140 header = "=" * 72
142 if block.name == "unknown":
143 name_str = ""
144 else:
145 name_str = f"- {block.name}"
147 ostream.write("\n")
148 ostream.write(header + "\n")
149 ostream.write(f"Model Statistics {name_str} \n")
150 ostream.write("\n")
151 ostream.write(f"Degrees of Freedom: {dof_info.degrees_of_freedom} \n")
152 ostream.write("\n")
153 ostream.write(f"Total No. Variables: {len(all_variables)} \n")
154 ostream.write(f"{tab}No. Fixed Variables: {len(fixed_variables)}\n")
155 ostream.write(
156 f"{tab}No. Unused Variables: {len(unused_variables)} (Fixed):"
157 f"{len(fixed_unused_variables)})\n"
158 )
159 ostream.write(
160 f"{tab}No. Variables only in Inequalities:"
161 f" {len(variables_only_in_inequalities)}"
162 f" (Fixed: {len(fixed_variables_only_in_inequalities)}) \n"
163 )
164 ostream.write("\n")
165 ostream.write(
166 f"Total No. Constraints: {idaes_model_statistics.number_total_constraints(block)} \n"
167 )
168 ostream.write(
169 f"{tab}No. Equality Constraints: "
170 f"{idaes_model_statistics.number_total_equalities(block)}"
171 f" (Deactivated: "
172 f"{idaes_model_statistics.number_deactivated_equalities(block)})"
173 f"\n"
174 )
175 ostream.write(
176 f"{tab}No. Inequality Constraints: "
177 f"{idaes_model_statistics.number_total_inequalities(block)}"
178 f" (Deactivated: "
179 f"{idaes_model_statistics.number_deactivated_inequalities(block)})"
180 f"\n"
181 )
182 ostream.write("\n")
183 ostream.write(
184 f"No. Objectives: "
185 f"{idaes_model_statistics.number_total_objectives(block)}"
186 f" (Deactivated: "
187 f"{idaes_model_statistics.number_deactivated_objectives(block)})"
188 f"\n"
189 )
190 ostream.write("\n")
191 ostream.write(
192 f"No. Blocks: {idaes_model_statistics.number_total_blocks(block)}"
193 f" (Deactivated: "
194 f"{idaes_model_statistics.number_deactivated_blocks(block)}) \n"
195 )
196 ostream.write(
197 f"No. Expressions: {idaes_model_statistics.number_expressions(block)} \n"
198 )
199 if idaes_model_statistics.number_activated_greybox_blocks(block) != 0: 199 ↛ 200line 199 didn't jump to line 200 because the condition on line 199 was never true
200 ostream.write(
201 "No. Activated GreyBox Blocks: "
202 f"{idaes_model_statistics.number_activated_greybox_blocks(block)} \n"
203 )
204 ostream.write(
205 f"No. GreyBox Variables: {idaes_model_statistics.number_of_greybox_variables(block)} \n"
206 )
207 ostream.write(
208 "No. Fixed GreyBox Variables: "
209 f"{idaes_model_statistics.number_of_greybox_variables(block) - idaes_model_statistics.number_of_unfixed_greybox_variables(block)} \n"
210 )
211 ostream.write(
212 "No. GreyBox Equalities: "
213 f"{idaes_model_statistics.number_activated_greybox_equalities(block)} \n"
214 )
215 ostream.write(header + "\n")
216 ostream.write("\n")
219_ORIGINAL_DEGREES_OF_FREEDOM = idaes_model_statistics.degrees_of_freedom
220_ORIGINAL_NUMBER_UNFIXED = (
221 idaes_model_statistics.number_unfixed_variables_in_activated_equalities
222)
223_ORIGINAL_UNFIXED_SET = idaes_model_statistics.unfixed_variables_in_activated_equalities_set
224_ORIGINAL_REPORT_STATISTICS = idaes_model_statistics.report_statistics
225_PATCH_INSTALLED = False
228def _rebind_loaded_imports(attribute_name: str, original, replacement) -> None:
229 for module in tuple(sys.modules.values()):
230 if module is None: 230 ↛ 231line 230 didn't jump to line 231 because the condition on line 230 was never true
231 continue
232 try:
233 current = getattr(module, attribute_name, None)
234 except Exception:
235 continue
236 if current is original:
237 setattr(module, attribute_name, replacement)
240def install_model_statistics_optimizations() -> None:
241 global _PATCH_INSTALLED
243 if _PATCH_INSTALLED: 243 ↛ 244line 243 didn't jump to line 244 because the condition on line 243 was never true
244 return
246 idaes_model_statistics.degrees_of_freedom = degrees_of_freedom
247 idaes_model_statistics.number_unfixed_variables_in_activated_equalities = (
248 number_unfixed_variables_in_activated_equalities
249 )
250 idaes_model_statistics.unfixed_variables_in_activated_equalities_set = (
251 unfixed_variables_in_activated_equalities_set
252 )
253 idaes_model_statistics.report_statistics = report_statistics
255 _rebind_loaded_imports(
256 "degrees_of_freedom",
257 _ORIGINAL_DEGREES_OF_FREEDOM,
258 degrees_of_freedom,
259 )
260 _rebind_loaded_imports(
261 "number_unfixed_variables_in_activated_equalities",
262 _ORIGINAL_NUMBER_UNFIXED,
263 number_unfixed_variables_in_activated_equalities,
264 )
265 _rebind_loaded_imports(
266 "unfixed_variables_in_activated_equalities_set",
267 _ORIGINAL_UNFIXED_SET,
268 unfixed_variables_in_activated_equalities_set,
269 )
270 _rebind_loaded_imports(
271 "report_statistics",
272 _ORIGINAL_REPORT_STATISTICS,
273 report_statistics,
274 )
276 _PATCH_INSTALLED = True