Coverage for backend/idaes_service/solver/custom/energy/bus.py: 86%
53 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-06 23:27 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-06 23:27 +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
25# Set up logger
26_log = idaeslog.getLogger(__name__)
29# When using this file the name "Bus" is what is imported
30@declare_process_block_class("Bus")
31class BusData(UnitModelBlockData):
32 """
33 Zero order bus model
34 """
36 # CONFIG are options for the unit model, this simple model only has the mandatory config options
37 CONFIG = ConfigBlock()
39 CONFIG.declare(
40 "dynamic",
41 ConfigValue(
42 domain=In([False]),
43 default=False,
44 description="Dynamic model flag - must be False",
45 doc="""Indicates whether this model will be dynamic or not,
46 **default** = False. The Bus unit does not support dynamic
47 behavior, thus this must be False.""",
48 ),
49 )
50 CONFIG.declare(
51 "has_holdup",
52 ConfigValue(
53 default=False,
54 domain=In([False]),
55 description="Holdup construction flag - must be False",
56 doc="""Indicates whether holdup terms should be constructed or not.
57 **default** - False. The Bus unit does not have defined volume, thus
58 this must be False.""",
59 ),
60 )
61 CONFIG.declare(
62 "property_package",
63 ConfigValue(
64 default=useDefault,
65 domain=is_physical_parameter_block,
66 description="Property package to use for control volume",
67 doc="""Property parameter object used to define property calculations,
68 **default** - useDefault.
69 **Valid values:** {
70 **useDefault** - use default package from parent model or flowsheet,
71 **PhysicalParameterObject** - a PhysicalParameterBlock object.}""",
72 ),
73 )
74 CONFIG.declare(
75 "property_package_args",
76 ConfigBlock(
77 implicit=True,
78 description="Arguments to use for constructing property packages",
79 doc="""A ConfigBlock with arguments to be passed to a property block(s)
80 and used when constructing these,
81 **default** - None.
82 **Valid values:** {
83 see property package for documentation.}""",
84 ),
85 )
86 CONFIG.declare(
87 "num_inlets",
88 ConfigValue(
89 default=False,
90 domain=int,
91 description="Number of inlets to add",
92 doc="""Number of inlets to add""",
93 ),
94 )
96 def build(self):
97 # build always starts by calling super().build()
98 # This triggers a lot of boilerplate in the background for you
99 super().build()
101 # This creates blank scaling factors, which are populated later
102 self.scaling_factor = Suffix(direction=Suffix.EXPORT)
105 # Defining parameters of state block class
106 tmp_dict = dict(**self.config.property_package_args)
107 tmp_dict["parameters"] = self.config.property_package
108 tmp_dict["defined_state"] = True # inlet block is an inlet
110 # Add state blocks for inlet, outlet, and waste
111 # These include the state variables and any other properties on demand
112 num_inlets = self.config.num_inlets
114 self.inlet_list = [ "inlet_" + str(i+1) for i in range(num_inlets) ]
117 self.inlet_blocks = []
118 for name in self.inlet_list:
119 # add properties_inlet_1, properties_inlet2 etc
120 state_block = self.config.property_package.state_block_class(
121 self.flowsheet().config.time, doc="inlet power", **tmp_dict
122 )
123 self.inlet_blocks.append(state_block)
124 # Dynamic equivalent to self.properties_inlet_1 = stateblock
125 setattr(self,"properties_" + name, state_block)
126 # also add the port
127 self.add_port(name=name,block=state_block)
130 # Add outlet state block
131 tmp_dict["defined_state"] = False # outlet and waste block is not an inlet
132 self.properties_out = self.config.property_package.state_block_class(
133 self.flowsheet().config.time,
134 doc="Material properties of outlet",
135 **tmp_dict
136 )
138 # Add outlet port
139 self.add_port(name="outlet", block=self.properties_out)
142 # Add constraints
143 # Usually unit models use a control volume to do the mass, energy, and momentum
144 # balances, however, they will be explicitly written out in this example
145 @self.Constraint(
146 self.flowsheet().time,
147 doc="Power usage",
148 )
149 def eq_power_balance(b, t):
150 return (
151 sum(
152 state_block[t].power for state_block in self.inlet_blocks
153 )
154 == b.properties_out[t].power
155 )
158 def calculate_scaling_factors(self):
159 super().calculate_scaling_factors()
161 def initialize(blk, *args, **kwargs):
162 # Just propagate the power from inlet to outlet, good simple method of initialization
163 for t in blk.flowsheet().time:
164 power_in = 0
165 for state_block in blk.inlet_blocks:
166 power_in += state_block[t].power.value
167 if not blk.properties_out[t].power.fixed: 167 ↛ 163line 167 didn't jump to line 163 because the condition on line 167 was always true
168 blk.properties_out[t].power = power_in
170 def _get_stream_table_contents(self, time_point=0):
171 """
172 Assume unit has standard configuration of 1 inlet and 1 outlet.
174 Developers should overload this as appropriate.
175 """
177 io_dict = {}
178 for inlet_name in self.inlet_list:
179 io_dict[inlet_name] = getattr(self, inlet_name) # get a reference to the port
181 io_dict["Outlet"] = self.outlet
182 return create_stream_table_dataframe(io_dict, time_point=time_point)