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

1from __future__ import annotations 

2 

3from dataclasses import dataclass 

4import sys 

5 

6from pyomo.common.collections import ComponentSet 

7from pyomo.core.expr import identify_variables 

8 

9import idaes.core.util.model_statistics as idaes_model_statistics 

10 

11 

12@dataclass(frozen=True) 

13class DegreesOfFreedomInfo: 

14 active_equalities: int 

15 unfixed_variables_in_activated_equalities: int 

16 degrees_of_freedom: int 

17 

18 

19@dataclass(frozen=True) 

20class VariableUsageInfo: 

21 all_variables: ComponentSet 

22 active_constraint_variables: ComponentSet 

23 equality_variables: ComponentSet 

24 inequality_variables: ComponentSet 

25 

26 

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 

35 

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) 

44 

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) 

48 

49 return active_equalities, var_set 

50 

51 

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() 

58 

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) 

63 

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 

75 

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) 

83 

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) 

88 

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 ) 

95 

96 

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 ) 

107 

108 

109def unfixed_variables_in_activated_equalities_set(block): 

110 _, unfixed_var_set = _collect_unfixed_variables_in_activated_equalities(block) 

111 return unfixed_var_set 

112 

113 

114def number_unfixed_variables_in_activated_equalities(block): 

115 return degrees_of_freedom_info(block).unfixed_variables_in_activated_equalities 

116 

117 

118def degrees_of_freedom(block): 

119 return degrees_of_freedom_info(block).degrees_of_freedom 

120 

121 

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 

125 

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) 

138 

139 tab = " " * 4 

140 header = "=" * 72 

141 

142 if block.name == "unknown": 

143 name_str = "" 

144 else: 

145 name_str = f"- {block.name}" 

146 

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") 

217 

218 

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 

226 

227 

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) 

238 

239 

240def install_model_statistics_optimizations() -> None: 

241 global _PATCH_INSTALLED 

242 

243 if _PATCH_INSTALLED: 243 ↛ 244line 243 didn't jump to line 244 because the condition on line 243 was never true

244 return 

245 

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 

254 

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 ) 

275 

276 _PATCH_INSTALLED = True