Coverage for backend/ahuora-builder/src/ahuora_builder/custom/energy/energy_mixer.py: 82%
61 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 stringprep import in_table_a1
3from pyomo.environ import (
4 Var,
5 Suffix,
6 units as pyunits,
7)
8from pyomo.common.config import ConfigBlock, ConfigValue, In
10# Import IDAES cores
11from idaes.core import (
12 declare_process_block_class,
13 UnitModelBlockData,
14 useDefault,
15)
16from idaes.core.util.config import is_physical_parameter_block
17import idaes.core.util.scaling as iscale
18import idaes.logger as idaeslog
20from idaes.core.util.tables import create_stream_table_dataframe
22from idaes.core.util.exceptions import ConfigurationError, BurntToast
24# Set up logger
25_log = idaeslog.getLogger(__name__)
28# When using this file the name "EnergyMixer" is what is imported
29@declare_process_block_class("EnergyMixer")
30class EnergyMixerData(UnitModelBlockData):
31 """
32 Zero order energy_mixer model
33 """
35 # CONFIG are options for the unit model, this simple model only has the mandatory config options
36 CONFIG = ConfigBlock()
38 CONFIG.declare(
39 "dynamic",
40 ConfigValue(
41 domain=In([False]),
42 default=False,
43 description="Dynamic model flag - must be False",
44 doc="""Indicates whether this model will be dynamic or not,
45 **default** = False. The Bus unit does not support dynamic
46 behavior, thus this must be False.""",
47 ),
48 )
49 CONFIG.declare(
50 "has_holdup",
51 ConfigValue(
52 default=False,
53 domain=In([False]),
54 description="Holdup construction flag - must be False",
55 doc="""Indicates whether holdup terms should be constructed or not.
56 **default** - False. The Bus unit does not have defined volume, thus
57 this must be False.""",
58 ),
59 )
60 CONFIG.declare(
61 "property_package",
62 ConfigValue(
63 default=useDefault,
64 domain=is_physical_parameter_block,
65 description="Property package to use for control volume",
66 doc="""Property parameter object used to define property calculations,
67 **default** - useDefault.
68 **Valid values:** {
69 **useDefault** - use default package from parent model or flowsheet,
70 **PhysicalParameterObject** - a PhysicalParameterBlock object.}""",
71 ),
72 )
73 CONFIG.declare(
74 "property_package_args",
75 ConfigBlock(
76 implicit=True,
77 description="Arguments to use for constructing property packages",
78 doc="""A ConfigBlock with arguments to be passed to a property block(s)
79 and used when constructing these,
80 **default** - None.
81 **Valid values:** {
82 see property package for documentation.}""",
83 ),
84 )
85 CONFIG.declare(
86 "num_inlets",
87 ConfigValue(
88 default=False,
89 domain=int,
90 description="Number of inlets to add",
91 doc="""Number of inlets to add""",
92 ),
93 )
95 def build(self):
96 # build always starts by calling super().build()
97 # This triggers a lot of boilerplate in the background for you
98 super().build()
100 # This creates blank scaling factors, which are populated later
101 self.scaling_factor = Suffix(direction=Suffix.EXPORT)
104 # Defining parameters of state block class
105 tmp_dict = dict(**self.config.property_package_args)
106 tmp_dict["parameters"] = self.config.property_package
107 tmp_dict["defined_state"] = True # inlet block is an inlet
109 # Add state blocks for inlet, outlet, and waste
110 # These include the state variables and any other properties on demand
111 num_inlets = self.config.num_inlets
113 self.inlet_list = [ "inlet_" + str(i+1) for i in range(num_inlets) ]
116 self.inlet_blocks = []
117 for name in self.inlet_list:
118 # add properties_inlet_1, properties_inlet2 etc
119 state_block = self.config.property_package.state_block_class(
120 self.flowsheet().config.time, doc="inlet power", **tmp_dict
121 )
122 self.inlet_blocks.append(state_block)
123 # Dynamic equivalent to self.properties_inlet_1 = stateblock
124 setattr(self,"properties_" + name, state_block)
125 # also add the port
126 self.add_port(name=name,block=state_block)
129 # Add outlet state block
130 tmp_dict["defined_state"] = False # outlet and waste block is not an inlet
131 self.properties_out = self.config.property_package.state_block_class(
132 self.flowsheet().config.time,
133 doc="Material properties of outlet",
134 **tmp_dict
135 )
138 # Add outlet port
139 self.add_port(name="outlet", block=self.properties_out)
141 #Add variables for capacity and efficiency:
142 self.efficiency = Var(self.flowsheet().config.time,
143 initialize=1.0,
144 doc="Efficiency of the link",
145 )
146 self.capacity = Var(
147 initialize=1.0,
148 units = pyunits.W,
149 doc="Capacity of the link",
150 )
153 # Add constraints
154 # Usually unit models use a control volume to do the mass, energy, and momentum
155 # balances, however, they will be explicitly written out in this example
157 @self.Constraint(
158 self.flowsheet().time,
159 doc="Power usage",
160 )
161 def eq_power_balance(b, t):
162 return (
163 sum(
164 state_block[t].power for state_block in self.inlet_blocks
165 ) * self.efficiency[t]
166 == b.properties_out[t].power
167 )
169 def calculate_scaling_factors(self):
170 super().calculate_scaling_factors()
172 def initialize(blk, *args, **kwargs):
173 # Just propagate the power from inlet to outlet, good simple method of initialization
174 for t in blk.flowsheet().time:
175 power_in = 0
176 for state_block in blk.inlet_blocks:
177 power_in += state_block[t].power.value
178 if not blk.properties_out[t].power.fixed: 178 ↛ 180line 178 didn't jump to line 180 because the condition on line 178 was always true
179 blk.properties_out[t].power = power_in
180 if(power_in > blk.capacity.value): 180 ↛ 181line 180 didn't jump to line 181 because the condition on line 180 was never true
181 raise BurntToast(
182 "Danger: Input power exceeds energy mixer capacity. Please either increase capacity or lower input power.".format(blk.name)
183 )
184 def _get_stream_table_contents(self, time_point=0):
185 """
186 Assume unit has standard configuration of 1 inlet and 1 outlet.
188 Developers should overload this as appropriate.
189 """
191 io_dict = {}
192 for inlet_name in self.inlet_list:
193 io_dict[inlet_name] = getattr(self, inlet_name) # get a reference to the port
195 io_dict["Outlet"] = self.outlet
196 return create_stream_table_dataframe(io_dict, time_point=time_point)
198 @staticmethod
199 def ahuora_metadata():
200 from ahuora_unit_ops.json_config import (
201 JsonAdapterArgConfig,
202 JsonFrontendConfig,
203 JsonGraphicObjectConfig,
204 JsonIdaesAdapterConfig,
205 JsonPortAdapterConfig,
206 JsonPortConfig,
207 JsonPropertyConfig,
208 JsonPropertySetGroupConfig,
209 JsonUnitOpConfig,
210 )
212 return JsonUnitOpConfig(
213 key='energy_mixer',
214 objectType='energy_mixer',
215 enumMember='energy_mixer',
216 displayType='Energy Mixer',
217 displayName='Energy Mixer',
218 categoryPath=['energy', 'power_system'],
219 ports={
220 'inlet': JsonPortConfig(
221 displayName='Power Inlet',
222 type='inlet',
223 streamType='energy_stream',
224 many=True,
225 default=2,
226 minimum=2,
227 makeStream=True,
228 streamOffset=0.75,
229 streamName='PS',
230 ),
231 'outlet': JsonPortConfig(
232 displayName='Power Outlet',
233 type='outlet',
234 streamType='energy_stream',
235 many=False,
236 default=1,
237 minimum=1,
238 makeStream=True,
239 streamOffset=0.75,
240 streamName='PS',
241 ),
242 },
243 propertyPackagePorts={
244 '': ['inlet', 'outlet'],
245 },
246 graphicObject=JsonGraphicObjectConfig(
247 kind='unitop_graphic',
248 ),
249 indexSets=[],
250 properties={
251 'efficiency': JsonPropertyConfig(
252 propertySetGroup='default',
253 displayName='Efficiency',
254 indexSets=None,
255 sumToOne=False,
256 value=None,
257 unit=None,
258 unitType='ratio',
259 description=None,
260 type='numeric',
261 many=False,
262 default=1,
263 options={},
264 hasTimeIndex=True,
265 ),
266 'capacity': JsonPropertyConfig(
267 propertySetGroup='default',
268 displayName='Capacity',
269 indexSets=None,
270 sumToOne=False,
271 value=None,
272 unit=None,
273 unitType='heatflow',
274 description=None,
275 type='numeric',
276 many=False,
277 default=1,
278 options={},
279 hasTimeIndex=False,
280 ),
281 },
282 propertySetGroups={
283 'default': JsonPropertySetGroupConfig(
284 type='stateVars',
285 displayName='Properties',
286 stateVars=['efficiency', 'capacity'],
287 toggle=None,
288 ),
289 },
290 keyProperties=['efficiency'],
291 splitterFractionName=None,
292 idaesAdapter=JsonIdaesAdapterConfig(
293 constructor='ahuora_builder.custom.energy.energy_mixer.EnergyMixer',
294 args={
295 'property_package': JsonAdapterArgConfig(
296 kind='constant_schema',
297 schema='PowerPPArgSchema',
298 ),
299 'num_inlets': JsonAdapterArgConfig(
300 kind='port_count',
301 port='inlet',
302 ),
303 },
304 ports=JsonPortAdapterConfig(
305 kind='mixer',
306 ),
307 properties=None,
308 ),
309 frontend=JsonFrontendConfig(
310 showInPanel=True,
311 variant=None,
312 ),
313 )