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
« 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
22from ..custom.updated_pressure_changer import (
24 CompressorData,
25)
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 """
37 # CONFIG are options for the unit model, this simple model only has the mandatory config options
38 CONFIG = CompressorData.CONFIG()
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 )
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()
71 # This creates blank scaling factors, which are populated later
72 self.scaling_factor = Suffix(direction=Suffix.EXPORT)
75 # Add state blocks for inlet, outlet, and waste
76 # These include the state variables and any other properties on demand
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 # )
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 )
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)
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 )
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
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 )
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 )