Coverage for backend/ahuora-builder/src/ahuora_builder/custom/energy/grid.py: 35%
52 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-06-23 21:51 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-06-23 21:51 +0000
1# Import Pyomo libraries
2from pyomo.environ import (
3 Component,
4 Var,
5 Suffix,
6 value,
7 units as pyunits,
8)
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
22# Set up logger
23_log = idaeslog.getLogger(__name__)
26# When using this file the name "Grid" is what is imported
27@declare_process_block_class("Grid")
28class gridData(UnitModelBlockData):
29 """
30 Zero order Grid model
31 """
33 # CONFIG are options for the unit model, this simple model only has the mandatory config options
34 CONFIG = ConfigBlock()
36 CONFIG.declare(
37 "dynamic",
38 ConfigValue(
39 domain=In([False]),
40 default=False,
41 description="Dynamic model flag - must be False",
42 doc="""Indicates whether this model will be dynamic or not,
43 **default** = False. The Bus unit does not support dynamic
44 behavior, thus this must be False.""",
45 ),
46 )
47 CONFIG.declare(
48 "has_holdup",
49 ConfigValue(
50 default=False,
51 domain=In([False]),
52 description="Holdup construction flag - must be False",
53 doc="""Indicates whether holdup terms should be constructed or not.
54 **default** - False. The Bus unit does not have defined volume, thus
55 this must be False.""",
56 ),
57 )
58 CONFIG.declare(
59 "property_package",
60 ConfigValue(
61 default=useDefault,
62 domain=is_physical_parameter_block,
63 description="Property package to use for control volume",
64 doc="""Property parameter object used to define property calculations,
65 **default** - useDefault.
66 **Valid values:** {
67 **useDefault** - use default package from parent model or flowsheet,
68 **PhysicalParameterObject** - a PhysicalParameterBlock object.}""",
69 ),
70 )
71 CONFIG.declare(
72 "property_package_args",
73 ConfigBlock(
74 implicit=True,
75 description="Arguments to use for constructing property packages",
76 doc="""A ConfigBlock with arguments to be passed to a property block(s)
77 and used when constructing these,
78 **default** - None.
79 **Valid values:** {
80 see property package for documentation.}""",
81 ),
82 )
84 def build(self):
85 # build always starts by calling super().build()
86 # This triggers a lot of boilerplate in the background for you
87 super().build()
89 # This creates blank scaling factors, which are populated later
90 self.scaling_factor = Suffix(direction=Suffix.EXPORT)
93 # Add state blocks for inlet, outlet, and waste
94 # These include the state variables and any other properties on demand
95 # Add inlet block
96 tmp_dict = dict(**self.config.property_package_args)
97 tmp_dict["parameters"] = self.config.property_package
98 tmp_dict["defined_state"] = True # inlet block is an inlet
99 self.properties_in = self.config.property_package.state_block_class(
100 self.flowsheet().config.time, doc="Material properties of inlet", **tmp_dict
101 )
102 # Add outlet and waste block
103 tmp_dict["defined_state"] = False # outlet and waste block is not an inlet
104 self.properties_out = self.config.property_package.state_block_class(
105 self.flowsheet().config.time,
106 doc="Material properties of outlet",
107 **tmp_dict
108 )
110 # Add ports - oftentimes users interact with these rather than the state blocks
111 self.add_port(name="inlet", block=self.properties_in)
113 # Add variables
114 self.n_capacity = Var(self.flowsheet().config.time,
115 initialize=1.0,
116 doc="N Capacity",
117 units = pyunits.W
118 )
119 self.n_minus_one = Var(self.flowsheet().config.time,
120 initialize=1.0,
121 doc="N-1 Capacity",
122 units = pyunits.W
123 )
124 self.import_export = Var(self.flowsheet().config.time,
125 initialize=1.0,
126 doc="Power being import or exported from grid",
127 units = pyunits.W
128 )
129 # Add constraints
130 # Usually unit models use a control volume to do the mass, energy, and momentum
131 # balances, however, they will be explicitly written out in this example
132 @self.Constraint(
133 self.flowsheet().time,
134 doc="Power usage",
135 )
136 def eq_power_in_balance(b, t):
137 return (
138 self.import_export[t] == b.properties_in[t].power
139 )
141 def calculate_scaling_factors(self):
142 super().calculate_scaling_factors()
144 def initialize(blk, *args, **kwargs):
145 # Just propagate the power from inlet to outlet, good simple method of initialization
146 for i in blk.properties_in.index_set():
147 if not blk.properties_out[i].power.fixed:
148 blk.properties_out[i].power = blk.properties_in[i].power.value
150 def diagnose(self) -> list[tuple[Component, str]]:
151 """
152 Report common Grid configuration issues that can be shown in the
153 flowsheet diagnostics panel.
154 """
155 problems = []
156 for time in self.flowsheet().time:
157 power = value(self.import_export[time], exception=False)
158 capacity = value(self.n_capacity[time], exception=False)
160 if (
161 power is not None
162 and capacity is not None
163 and abs(power) > capacity
164 ):
165 problems.append(
166 (
167 self.n_capacity[time],
168 f"Grid power in/out ({power:.2f} W) exceeds Grid "
169 f"N-Capacity ({capacity:.2f} W). Increase the "
170 "capacity or reduce the grid power.",
171 )
172 )
173 # Avoid reporting this multiple times, so we're just going to early return.
174 return problems
176 return problems
178 def _get_stream_table_contents(self, time_point=0):
179 """
180 Assume unit has standard configuration of 1 inlet and 1 outlet.
182 Developers should overload this as appropriate.
183 """
184 try:
185 return create_stream_table_dataframe(
186 {"inlet": self.inlet}, time_point=time_point
187 )
188 except AttributeError:
189 raise ConfigurationError(
190 f"Unit model {self.name} does not have the standard Port "
191 f"names (inlet and outlet). Please contact the unit model "
192 f"developer to develop a unit specific stream table."
193 )