Coverage for backend/ahuora-builder/src/ahuora_builder/custom/custom_compressor.py: 89%

48 statements  

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

1# Import Pyomo libraries 

2from pyomo.environ import ( 

3 Component, 

4 Var, 

5 Suffix, 

6 units as pyunits, 

7) 

8import pyomo.environ as pyo 

9from pyomo.common.config import ConfigBlock, ConfigValue, In 

10from idaes.core.util.tables import create_stream_table_dataframe 

11from idaes.core.util.exceptions import ConfigurationError 

12# Import IDAES cores 

13from idaes.core import ( 

14 declare_process_block_class, 

15 UnitModelBlockData, 

16 useDefault, 

17) 

18from idaes.core.util.config import is_physical_parameter_block 

19import idaes.core.util.scaling as iscale 

20import idaes.logger as idaeslog 

21 

22from ..custom.updated_pressure_changer import ( 

23 

24 CompressorData, 

25) 

26 

27 

28 

29 

30# When using this file the name "CustomCompressor" is what is imported 

31@declare_process_block_class("CustomCompressor") 

32class CustomCompressorData(CompressorData): 

33 """ 

34 Zero order Load model 

35 """ 

36 

37 # CONFIG are options for the unit model, this simple model only has the mandatory config options 

38 CONFIG = CompressorData.CONFIG() 

39 

40 CONFIG.declare( 

41 "power_property_package", 

42 ConfigValue( 

43 default=useDefault, 

44 domain=is_physical_parameter_block, 

45 description="Property package to use for power", 

46 doc="""Power Property parameter object used to define power calculations, 

47 **default** - useDefault. 

48 **Valid values:** { 

49 **useDefault** - use default package from parent model or flowsheet, 

50 **PhysicalParameterObject** - a PhysicalParameterBlock object.}""", 

51 ), 

52 ) 

53 CONFIG.declare( 

54 "power_property_package_args", 

55 ConfigBlock( 

56 implicit=True, 

57 description="Arguments to use for constructing power property packages", 

58 doc="""A ConfigBlock with arguments to be passed to a property block(s) 

59 and used when constructing these, 

60 **default** - None.  

61 **Valid values:** { 

62 see property package for documentation.}""", 

63 ), 

64 ) 

65 

66 def build(self): 

67 # build always starts by calling super().build() 

68 # This triggers a lot of boilerplate in the background for you 

69 super().build() 

70 

71 # This creates blank scaling factors, which are populated later 

72 self.scaling_factor = Suffix(direction=Suffix.EXPORT) 

73 

74 

75 # Add state blocks for inlet, outlet, and waste 

76 # These include the state variables and any other properties on demand 

77 

78 tmp_dict = dict(**self.config.property_package_args) 

79 tmp_dict["parameters"] = self.config.property_package 

80 tmp_dict["defined_state"] = True # inlet block is an inlet 

81 # Add inlet block 

82 # self.properties_in = self.config.property_package.state_block_class( 

83 # self.flowsheet().config.time, doc="Material properties of inlet", **tmp_dict 

84 # ) 

85 

86 

87 # Add outlet and waste block 

88 tmp_dict["defined_state"] = False # outlet and waste block is not an inlet 

89 self.power_properties_out = self.config.power_property_package.state_block_class( 

90 self.flowsheet().config.time, 

91 doc="Material properties of outlet", 

92 **tmp_dict 

93 ) 

94 

95 # Add ports - oftentimes users interact with these rather than the state blocks 

96 self.add_port(name="power_outlet", block=self.power_properties_out) 

97 

98 # Add constraints 

99 # Usually unit models use a control volume to do the mass, energy, and momentum 

100 # balances, however, they will be explicitly written out in this example 

101 @self.Constraint( 

102 self.flowsheet().time, 

103 doc="Power out", 

104 ) 

105 def eq_power_out(b, t): 

106 return ( 

107 self.power_properties_out[t].power == self.work_mechanical[t] * -1 

108 ) 

109 

110 def diagnose(self) -> list[tuple[Component, str]]: 

111 """ 

112 Test a few common issues with the heat exchanger model and provide hints to the user. 

113 returns a list with the variable the it is most relevant to and a message describing the issue 

114 """ 

115 problems = [] 

116 inlet_vf = pyo.value(self.control_volume.properties_in[0].vapor_frac) or -1 

117 if inlet_vf < 0.9: 

118 problems.append( 

119 ( 

120 self.control_volume.properties_in[0].vapor_frac, 

121 f"""Inlet vapor fraction is {inlet_vf:.2f}. Compressors are typically used for gases, 

122 as liquids are incompressible. 

123 This low vapor fraction may indicate a problem with your formulation upstream.""" 

124 ) 

125 ) 

126 outlet_pressure = pyo.value(self.control_volume.properties_out[0].pressure) or 0 

127 outlet_pressure_bound = self.control_volume.properties_out[0].pressure.ub or 1e8 # 100 MPa 

128 if outlet_pressure > outlet_pressure_bound * 0.9: 128 ↛ 129line 128 didn't jump to line 129 because the condition on line 128 was never true

129 problems.append( 

130 ( 

131 self.work_mechanical, 

132 f"""Outlet pressure is {outlet_pressure:.2f} Pa, which is close to the upper bound of {outlet_pressure_bound:.2f} Pa. 

133 This may indicate that you are putting in 

134 an unreasonably large amount of work for this volume of gas""" 

135 ) 

136 ) 

137 ratioP = pyo.value(self.ratioP[0]) or 0 

138 ratioP_bound = self.ratioP[0].ub or 1e3 

139 if ratioP > ratioP_bound * 0.9: 139 ↛ 140line 139 didn't jump to line 140 because the condition on line 139 was never true

140 problems.append( 

141 ( 

142 self.work_mechanical, 

143 f"""Pressure ratio is {ratioP:.2f}, which is very high. 

144 This may indicate that you are putting in 

145 an unreasonably large amount of work for this volume of gas.""" 

146 ) 

147 ) 

148 inlet_pressure = pyo.value(self.control_volume.properties_in[0].pressure) or 0 

149 if outlet_pressure < inlet_pressure: 

150 problems.append( 

151 ( 

152 self.control_volume.properties_out[0].pressure, 

153 f"""Outlet pressure ({outlet_pressure:.2f} Pa) is less than inlet pressure ({inlet_pressure:.2f} Pa). 

154 Compressors should increase pressure, did you mean to use a turbine or valve instead?""" 

155 ) 

156 ) 

157 return problems 

158 

159 

160 @staticmethod 

161 def ahuora_metadata(): 

162 from ahuora_unit_ops.json_config import ( 

163 JsonAdapterArgConfig, 

164 JsonFrontendConfig, 

165 JsonGraphicObjectConfig, 

166 JsonIdaesAdapterConfig, 

167 JsonPortConfig, 

168 JsonPropertyConfig, 

169 JsonPropertySetGroupConfig, 

170 JsonUnitOpConfig, 

171 ) 

172 

173 return JsonUnitOpConfig( 

174 key='compressor', 

175 objectType='compressor', 

176 enumMember='Compressor', 

177 displayType='Compressor', 

178 displayName='Compressor', 

179 categoryPath=['chemical', 'pressure_changer'], 

180 ports={ 

181 'inlet': JsonPortConfig( 

182 displayName='Inlet', 

183 type='inlet', 

184 streamType='stream', 

185 many=False, 

186 default=1, 

187 minimum=1, 

188 makeStream=True, 

189 streamOffset=0.75, 

190 streamName='S', 

191 ), 

192 'outlet': JsonPortConfig( 

193 displayName='Outlet', 

194 type='outlet', 

195 streamType='stream', 

196 many=False, 

197 default=1, 

198 minimum=1, 

199 makeStream=True, 

200 streamOffset=0.75, 

201 streamName='S', 

202 ), 

203 'power_outlet': JsonPortConfig( 

204 displayName='Power Stream', 

205 type='outlet', 

206 streamType='energy_stream', 

207 many=False, 

208 default=1, 

209 minimum=1, 

210 makeStream=False, 

211 streamOffset=0.75, 

212 streamName='PS', 

213 ), 

214 }, 

215 propertyPackagePorts={ 

216 '': ['inlet', 'outlet'], 

217 }, 

218 graphicObject=JsonGraphicObjectConfig( 

219 kind='unitop_graphic', 

220 ), 

221 indexSets=[], 

222 properties={ 

223 'efficiency_isentropic': JsonPropertyConfig( 

224 propertySetGroup='default', 

225 displayName='Isentropic Efficiency', 

226 indexSets=None, 

227 sumToOne=False, 

228 value=None, 

229 unit=None, 

230 unitType='ratio', 

231 description=None, 

232 type='numeric', 

233 many=False, 

234 default=1, 

235 options={}, 

236 hasTimeIndex=True, 

237 ), 

238 'deltaP': JsonPropertyConfig( 

239 propertySetGroup='default', 

240 displayName='Pressure Increase', 

241 indexSets=None, 

242 sumToOne=False, 

243 value=None, 

244 unit=None, 

245 unitType='pressure', 

246 description=None, 

247 type='numeric', 

248 many=False, 

249 default=1, 

250 options={}, 

251 hasTimeIndex=True, 

252 ), 

253 'ratioP': JsonPropertyConfig( 

254 propertySetGroup='default', 

255 displayName='Pressure Ratio', 

256 indexSets=None, 

257 sumToOne=False, 

258 value=None, 

259 unit=None, 

260 unitType='ratio', 

261 description=None, 

262 type='numeric', 

263 many=False, 

264 default=1, 

265 options={}, 

266 hasTimeIndex=True, 

267 ), 

268 'work_mechanical': JsonPropertyConfig( 

269 propertySetGroup='default', 

270 displayName='Mechanical Work', 

271 indexSets=None, 

272 sumToOne=False, 

273 value=None, 

274 unit=None, 

275 unitType='heatflow', 

276 description=None, 

277 type='numeric', 

278 many=False, 

279 default=1, 

280 options={}, 

281 hasTimeIndex=True, 

282 ), 

283 }, 

284 propertySetGroups={ 

285 'default': JsonPropertySetGroupConfig( 

286 type='stateVars', 

287 displayName='Properties', 

288 stateVars=['efficiency_isentropic', 'work_mechanical'], 

289 toggle=None, 

290 ), 

291 }, 

292 keyProperties=['work_mechanical', 'efficiency_isentropic', 'deltaP', 'ratioP'], 

293 splitterFractionName=None, 

294 idaesAdapter=JsonIdaesAdapterConfig( 

295 constructor='ahuora_builder.custom.custom_compressor.CustomCompressor', 

296 args={ 

297 'property_package': JsonAdapterArgConfig( 

298 kind='property_package', 

299 label=None, 

300 ), 

301 'dynamic': JsonAdapterArgConfig( 

302 kind='constant', 

303 value=False, 

304 ), 

305 'power_property_package': JsonAdapterArgConfig( 

306 kind='constant_schema', 

307 schema='PowerPPArgSchema', 

308 ), 

309 }, 

310 ports=None, 

311 properties=None, 

312 ), 

313 frontend=JsonFrontendConfig( 

314 showInPanel=True, 

315 variant=None, 

316 ), 

317 )