Coverage for backend/idaes_service/solver/custom/energy/mainDistributionBoard.py: 81%
83 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 Set,
8 value
9)
10from pyomo.common.config import ConfigBlock, ConfigValue, In
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 idaes.core.util.tables import create_stream_table_dataframe
23from idaes.core.util.math import smooth_min
24from idaes.core.util.exceptions import ConfigurationError
26# Set up logger
27_log = idaeslog.getLogger(__name__)
30# When using this file the name "MDB" is what is imported
31@declare_process_block_class("MDB")
32class MDBData(UnitModelBlockData):
33 """
34 Zero order power distirbution board model
35 """
37 # CONFIG are options for the unit model, this simple model only has the mandatory config options
38 CONFIG = ConfigBlock()
40 CONFIG.declare(
41 "dynamic",
42 ConfigValue(
43 domain=In([False]),
44 default=False,
45 description="Dynamic model flag - must be False",
46 doc="""Indicates whether this model will be dynamic or not,
47 **default** = False. The Bus unit does not support dynamic
48 behavior, thus this must be False.""",
49 ),
50 )
51 CONFIG.declare(
52 "has_holdup",
53 ConfigValue(
54 default=False,
55 domain=In([False]),
56 description="Holdup construction flag - must be False",
57 doc="""Indicates whether holdup terms should be constructed or not.
58 **default** - False. The Bus unit does not have defined volume, thus
59 this must be False.""",
60 ),
61 )
62 CONFIG.declare(
63 "property_package",
64 ConfigValue(
65 default=useDefault,
66 domain=is_physical_parameter_block,
67 description="Property package to use for control volume",
68 doc="""Property parameter object used to define property calculations,
69 **default** - useDefault.
70 **Valid values:** {
71 **useDefault** - use default package from parent model or flowsheet,
72 **PhysicalParameterObject** - a PhysicalParameterBlock object.}""",
73 ),
74 )
75 CONFIG.declare(
76 "property_package_args",
77 ConfigBlock(
78 implicit=True,
79 description="Arguments to use for constructing property packages",
80 doc="""A ConfigBlock with arguments to be passed to a property block(s)
81 and used when constructing these,
82 **default** - None.
83 **Valid values:** {
84 see property package for documentation.}""",
85 ),
86 )
87 CONFIG.declare(
88 "num_inlets",
89 ConfigValue(
90 default=False,
91 domain=int,
92 description="Number of inlets to add",
93 doc="""Number of inlets to add""",
94 ),
95 )
96 CONFIG.declare(
97 "num_outlets",
98 ConfigValue(
99 default=False,
100 domain=int,
101 description="Number of outlets to add",
102 doc="""Number of outlets to add""",
103 ),
104 )
106 def build(self):
107 # build always starts by calling super().build()
108 # This triggers a lot of boilerplate in the background for you
109 super().build()
111 # This creates blank scaling factors, which are populated later
112 self.scaling_factor = Suffix(direction=Suffix.EXPORT)
115 # Defining parameters of state block class
116 tmp_dict = dict(**self.config.property_package_args)
117 tmp_dict["parameters"] = self.config.property_package
118 tmp_dict["defined_state"] = True # inlet block is an inlet
120 # Add state blocks for inlet, outlet, and waste
121 # These include the state variables and any other properties on demand
122 num_inlets = self.config.num_inlets
124 self.inlet_list = [ "inlet_" + str(i+1) for i in range(num_inlets) ]
127 self.inlet_blocks = []
128 for name in self.inlet_list:
129 # add properties_inlet_1, properties_inlet2 etc
130 state_block = self.config.property_package.state_block_class(
131 self.flowsheet().config.time, doc="inlet power", **tmp_dict
132 )
133 self.inlet_blocks.append(state_block)
134 # Dynamic equivalent to self.properties_inlet_1 = stateblock
135 setattr(self,"properties_" + name, state_block)
136 # also add the port
137 self.add_port(name=name,block=state_block)
140 num_outlets = self.config.num_outlets
141 self.outlet_list = [ "outlet_" + str(i+1) for i in range(num_outlets) ]
142 self.priority = [i for i in range(num_outlets)]
143 self.outlet_set = Set(initialize=self.outlet_list)
146 self.outlet_blocks = []
147 for name in self.outlet_list:
148 # add properties_outlet_1, properties_outlet2 etc
149 state_block = self.config.property_package.state_block_class(
150 self.flowsheet().config.time, doc="outlet power", **tmp_dict
151 )
152 self.outlet_blocks.append(state_block)
153 # Dynamic equivalent to self.properties_outlet_1 = stateblock
154 setattr(self,"properties_" + name, state_block)
155 # also add the port
156 self.add_port(name=name,block=state_block)
158 # Add variable for power splitting
159 self.priorities= Var(
160 self.flowsheet().time,
161 self.outlet_set,
162 initialize=1.0,
163 units = pyunits.W,
164 doc="How the power is split between outlets depending on priority",
165 )
166 #Add array to store power demand at each outlet
167 self.demand = []
169 self.available = Var(
170 self.flowsheet().time,
171 self.outlet_list,
172 initialize = 1.0,
173 units = pyunits.dimensionless
174 )
176 #Expression for total power supply:
177 @self.Expression(
178 self.flowsheet().time
179 )
180 def total_power(b, t):
181 return (
182 sum(
183 state_block[t].power for state_block in self.inlet_blocks
184 )
185 )
187 #Expression for total power demand:
188 @self.Expression(
189 self.flowsheet().time
190 )
191 def total_power_demand(b, t):
192 return (
193 sum(
194 state_block[t].power for state_block in self.outlet_blocks
195 )
196 )
198 #Constraint for available power:
199 @self.Constraint(
200 self.flowsheet().time,
201 self.outlet_list,
202 doc="expression for calculating available power"
203 )
204 def eq_available_power(b,t,o):
205 p = self.outlet_list.index(o)
206 if o == self.outlet_list[0]:
207 return self.available[t,o] == self.total_power[t]
208 else:
209 outlet_block = getattr(self,"properties_" + self.outlet_list[p-1])
210 return self.available[t,o] == self.available[t,self.outlet_list[p-1]] - outlet_block[t].power
213 @self.Constraint(
214 self.flowsheet().time,
215 self.outlet_list,
216 doc = "power out"
217 )
218 def eq_power_out(b,t,o):
219 outlet_block = getattr(self,"properties_" + o)
220 if o == self.outlet_list[-1]:
221 return outlet_block[t].power == self.available[t,o]
222 else:
223 return outlet_block[t].power == smooth_min(self.priorities[t,o], self.available[t,o])
225 @self.Constraint(
226 self.flowsheet().time,
227 doc = "Power at last outlet"
228 )
229 def eq_last_outlet(b,t):
230 return self.priorities[t, self.outlet_list[-1]] == self.available[t,self.outlet_list[-1]]
233 def calculate_scaling_factors(self):
234 super().calculate_scaling_factors()
237 def initialize(blk, *args, **kwargs):
238 for t in blk.flowsheet().time:
239 power_in = 0
240 for state_block in blk.inlet_blocks:
241 power_in += state_block[t].power.value
244 def _get_stream_table_contents(self, time_point=0):
245 """
246 Assume unit has standard configuration of 1 inlet and 1 outlet.
248 Developers should overload this as appropriate.
249 """
251 io_dict = {}
252 for inlet_name in self.inlet_list:
253 io_dict[inlet_name] = getattr(self, inlet_name) # get a reference to the port
255 io_dict = {}
256 for outlet_name in self.outlet_list:
257 io_dict[outlet_name] = getattr(self, outlet_name)