Coverage for backend/idaes_service/solver/custom/energy/power_property_package.py: 51%
70 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 tools
2from pyomo.environ import (
3 Constraint,
4 Var,
5 Param,
6 Expression,
7 Reals,
8 NonNegativeReals,
9 Suffix,
10)
11from pyomo.environ import units as pyunits
12from pyomo.common.config import ConfigBlock, ConfigValue, Bool
14# Import IDAES cores
15from idaes.core import (
16 declare_process_block_class,
17 MaterialFlowBasis,
18 PhysicalParameterBlock,
19 StateBlockData,
20 StateBlock,
21 MaterialBalanceType,
22 EnergyBalanceType,
23)
24from idaes.core.base.components import Component
25from idaes.core.base.phases import LiquidPhase
26from idaes.core.util.initialization import (
27 fix_state_vars,
28 revert_state_vars,
29 solve_indexed_blocks,
30)
31from idaes.core.base.process_base import ProcessBlockData
32from idaes.core.base import property_meta
33from idaes.core.util.model_statistics import (
34 degrees_of_freedom,
35 number_unfixed_variables,
36)
37from idaes.core.util.exceptions import PropertyPackageError
38import idaes.core.util.scaling as iscale
39import idaes.logger as idaeslog
40from idaes.core.solvers import get_solver
42# Set up logger
43_log = idaeslog.getLogger(__name__)
46# STEP 2
48# When using this file the name "PowerParameterBlock" is what is imported
49@declare_process_block_class("PowerParameterBlock")
50class PowerParameterData(PhysicalParameterBlock):
51 CONFIG = ProcessBlockData.CONFIG()
52 CONFIG.declare(
53 "default_arguments",
54 ConfigBlock(
55 implicit=True, description="Default arguments to use with Property Package"
56 ),
57 )
59 def build(self):
60 """
61 Callable method for Block construction.
62 """
63 super(PowerParameterData, self).build()
65 self._state_block_class = PowerStateBlock
67 # Variables
68 self.power = Var(initialize=0, domain=Reals, units=pyunits.W)
70 # Default scaling values should be provided so that our tools can ensure the model is well-scaled
71 # Generally scaling factors should be such that if it is multiplied by the variable it will range between 0.01 and 100
72 self.set_default_scaling("power", 1e-3)
74 @classmethod
75 def define_metadata(cls, obj):
76 # see https://github.com/watertap-org/watertap/blob/main/tutorials/creating_a_simple_property_model.ipynb
77 obj.add_properties(
78 {
79 "power": {"method": None},
80 }
81 )
82 obj.add_default_units(
83 {
84 "time": pyunits.s,
85 "length": pyunits.m,
86 "mass": pyunits.kg,
87 }
88 )
90 def build_state_block(self, *args, **kwargs):
91 """
92 Methods to construct a StateBlock associated with this
93 PhysicalParameterBlock. This will automatically set the parameters
94 construction argument for the StateBlock.
96 Returns:
97 StateBlock
99 """
100 # default = kwargs.pop("default", {})
101 initialize = kwargs.pop("initialize", {})
103 if initialize == {}:
104 kwargs["parameters"] = self
105 else:
106 for i in initialize.keys():
107 initialize[i]["parameters"] = self
109 return self.state_block_class( # pylint: disable=not-callable
110 *args, **kwargs, initialize=initialize
111 )
113 @property
114 def state_block_class(self):
115 if self._state_block_class is not None: 115 ↛ 118line 115 didn't jump to line 118 because the condition on line 115 was always true
116 return self._state_block_class
117 else:
118 raise AttributeError(
119 "{} has not assigned a StateBlock class to be associated "
120 "with this property package. Please contact the developer of "
121 "the property package.".format(self.name)
122 )
124# STEP 3: State Block
125class _PowerStateBlock(StateBlock):
126 def initialize(
127 self,
128 state_args=None,
129 state_vars_fixed=False,
130 hold_state=False,
131 outlvl=idaeslog.NOTSET,
132 solver=None,
133 optarg=None,
134 ):
135 """
136 Initialization routine for property package.
137 Keyword Arguments:
138 state_args : Dictionary with initial guesses for the state vars
139 chosen. Note that if this method is triggered
140 through the control volume, and if initial guesses
141 were not provided at the unit model level, the
142 control volume passes the inlet values as initial
143 guess.The keys for the state_args dictionary are:
145 flow_mass_phase_comp : value at which to initialize
146 phase component flows
147 pressure : value at which to initialize pressure
148 temperature : value at which to initialize temperature
150 state_vars_fixed: Flag to denote if state vars have already been
151 fixed.
152 - True - states have already been fixed by the
153 control volume 1D. Control volume 0D
154 does not fix the state vars, so will
155 be False if this state block is used
156 with 0D blocks.
157 - False - states have not been fixed. The state
158 block will deal with fixing/unfixing.
159 hold_state : flag indicating whether the initialization routine
160 should unfix any state variables fixed during
161 initialization (default=False).
162 - True - states variables are not unfixed, and
163 a dict of returned containing flags for
164 which states were fixed during
165 initialization.
166 - False - state variables are unfixed after
167 initialization by calling the
168 release_state method
169 outlvl : sets output level of initialization routine (default=idaeslog.NOTSET)
170 solver : Solver object to use during initialization if None is provided
171 it will use the default solver for IDAES (default = None)
172 optarg : solver options dictionary object (default=None)
173 Returns:
174 If hold_states is True, returns a dict containing flags for
175 which states were fixed during initialization.
176 """
178 # Fix state variables
179 flags = fix_state_vars(self, state_args)
180 # Check that dof = 0 when state variables are fixed
181 for k in self.keys():
182 dof = degrees_of_freedom(self[k])
183 if dof != 0:
184 raise PropertyPackageError(
185 "\nWhile initializing {sb_name}, the degrees of freedom "
186 "are {dof}, when zero is required. \nInitialization assumes "
187 "that the state variables should be fixed and that no other "
188 "variables are fixed. \nIf other properties have a "
189 "predetermined value, use the calculate_state method "
190 "before using initialize to determine the values for "
191 "the state variables and avoid fixing the property variables."
192 "".format(sb_name=self.name, dof=dof)
193 )
195 # If input block, return flags, else release state
196 if state_vars_fixed is False:
197 if hold_state is True:
198 return flags
199 else:
200 self.release_state(flags)
202 def release_state(self, flags, outlvl=idaeslog.NOTSET):
203 """
204 Method to release state variables fixed during initialisation.
206 Keyword Arguments:
207 flags : dict containing information of which state variables
208 were fixed during initialization, and should now be
209 unfixed. This dict is returned by initialize if
210 hold_state=True.
211 outlvl : sets output level of of logging
212 """
213 if flags is None:
214 return
215 # Unfix state variables
216 for attr in flags:
217 if flags[attr] is True:
218 getattr(self, attr).unfix()
219 return
221# STEP 4:
222@declare_process_block_class("PowerStateBlock", block_class=_PowerStateBlock)
223class PowerStateBlockData(StateBlockData):
224 def build(self):
225 """Callable method for Block construction."""
226 super(PowerStateBlockData, self).build()
228 self.scaling_factor = Suffix(direction=Suffix.EXPORT)
230 self.power = Var(
231 initialize=0,
232 domain=Reals,
233 units=pyunits.W,
234 doc="Power flow",
235 )
237 # -----------------------------------------------------------------------------
239 def define_state_vars(self):
240 """Define state vars."""
241 return {
242 "power": self.power,
243 }
245 # -----------------------------------------------------------------------------
246 # Scaling methods
247 def calculate_scaling_factors(self):
248 super().calculate_scaling_factors()
249 # This doesn't do anything, but it's a good example of how to get and set scaling factors in relation to each other.
250 sf = iscale.get_scaling_factor(self.power)
251 iscale.set_scaling_factor(self.power, sf)