Coverage for backend/idaes_service/solver/custom/energy/transformer_property_package.py: 41%
75 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 "transformerParameterBlock" is what is imported
49@declare_process_block_class("transformerParameterBlock")
50class transformerParameterData(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(transformerParameterData, self).build()
65 self._state_block_class = transformerStateBlock
67 # Variables
68 self.current = Var(initialize=0, domain=Reals, units=pyunits.A)
69 self.voltage = Var(initialize=0, domain=Reals, units=pyunits.V)
71 # Default scaling values should be provided so that our tools can ensure the model is well-scaled
72 # Generally scaling factors should be such that if it is multiplied by the variable it will range between 0.01 and 100
73 self.set_default_scaling("current", 1e-3)
74 self.set_default_scaling("voltage", 1e-3)
76 @classmethod
77 def define_metadata(cls, obj):
78 # see https://github.com/watertap-org/watertap/blob/main/tutorials/creating_a_simple_property_model.ipynb
79 obj.add_properties(
80 {
81 "current": {"method": None},
82 "voltage": {"method": None},
83 }
84 )
85 obj.add_default_units(
86 {
87 "time": pyunits.s,
88 "length": pyunits.m,
89 "mass": pyunits.kg,
90 }
91 )
93 def build_state_block(self, *args, **kwargs):
94 """
95 Methods to construct a StateBlock associated with this
96 PhysicalParameterBlock. This will automatically set the parameters
97 construction argument for the StateBlock.
99 Returns:
100 StateBlock
102 """
103 # default = kwargs.pop("default", {})
104 initialize = kwargs.pop("initialize", {})
106 if initialize == {}:
107 kwargs["parameters"] = self
108 else:
109 for i in initialize.keys():
110 initialize[i]["parameters"] = self
112 return self.state_block_class( # pylint: disable=not-callable
113 *args, **kwargs, initialize=initialize
114 )
116 @property
117 def state_block_class(self):
118 if self._state_block_class is not None:
119 return self._state_block_class
120 else:
121 raise AttributeError(
122 "{} has not assigned a StateBlock class to be associated "
123 "with this property package. Please contact the developer of "
124 "the property package.".format(self.name)
125 )
127# STEP 3: State Block
128class _transformerStateBlock(StateBlock):
129 def initialize(
130 self,
131 state_args=None,
132 state_vars_fixed=False,
133 hold_state=False,
134 outlvl=idaeslog.NOTSET,
135 solver=None,
136 optarg=None,
137 ):
138 """
139 Initialization routine for property package.
140 Keyword Arguments:
141 state_args : Dictionary with initial guesses for the state vars
142 chosen. Note that if this method is triggered
143 through the control volume, and if initial guesses
144 were not provided at the unit model level, the
145 control volume passes the inlet values as initial
146 guess.The keys for the state_args dictionary are:
148 flow_mass_phase_comp : value at which to initialize
149 phase component flows
150 pressure : value at which to initialize pressure
151 temperature : value at which to initialize temperature
153 state_vars_fixed: Flag to denote if state vars have already been
154 fixed.
155 - True - states have already been fixed by the
156 control volume 1D. Control volume 0D
157 does not fix the state vars, so will
158 be False if this state block is used
159 with 0D blocks.
160 - False - states have not been fixed. The state
161 block will deal with fixing/unfixing.
162 hold_state : flag indicating whether the initialization routine
163 should unfix any state variables fixed during
164 initialization (default=False).
165 - True - states variables are not unfixed, and
166 a dict of returned containing flags for
167 which states were fixed during
168 initialization.
169 - False - state variables are unfixed after
170 initialization by calling the
171 release_state method
172 outlvl : sets output level of initialization routine (default=idaeslog.NOTSET)
173 solver : Solver object to use during initialization if None is provided
174 it will use the default solver for IDAES (default = None)
175 optarg : solver options dictionary object (default=None)
176 Returns:
177 If hold_states is True, returns a dict containing flags for
178 which states were fixed during initialization.
179 """
181 # Fix state variables
182 flags = fix_state_vars(self, state_args)
183 # Check that dof = 0 when state variables are fixed
184 for k in self.keys():
185 dof = degrees_of_freedom(self[k])
186 if dof != 0:
187 raise PropertyPackageError(
188 "\nWhile initializing {sb_name}, the degrees of freedom "
189 "are {dof}, when zero is required. \nInitialization assumes "
190 "that the state variables should be fixed and that no other "
191 "variables are fixed. \nIf other properties have a "
192 "predetermined value, use the calculate_state method "
193 "before using initialize to determine the values for "
194 "the state variables and avoid fixing the property variables."
195 "".format(sb_name=self.name, dof=dof)
196 )
198 # If input block, return flags, else release state
199 if state_vars_fixed is False:
200 if hold_state is True:
201 return flags
202 else:
203 self.release_state(flags)
205 def release_state(self, flags, outlvl=idaeslog.NOTSET):
206 """
207 Method to release state variables fixed during initialisation.
209 Keyword Arguments:
210 flags : dict containing information of which state variables
211 were fixed during initialization, and should now be
212 unfixed. This dict is returned by initialize if
213 hold_state=True.
214 outlvl : sets output level of of logging
215 """
216 if flags is None:
217 return
218 # Unfix state variables
219 for attr in flags:
220 if flags[attr] is True:
221 getattr(self, attr).unfix()
222 return
224# STEP 4:
225@declare_process_block_class("transformerStateBlock", block_class=_transformerStateBlock)
226class transformerStateBlockData(StateBlockData):
227 def build(self):
228 """Callable method for Block construction."""
229 super(transformerStateBlockData, self).build()
231 self.scaling_factor = Suffix(direction=Suffix.EXPORT)
233 self.current = Var(
234 initialize=0,
235 domain=Reals,
236 units=pyunits.A,
237 doc="reactive power flow",
238 )
239 self.voltage = Var(
240 initialize=0,
241 domain=Reals,
242 units=pyunits.V,
243 doc="voltage",
244 )
246 # -----------------------------------------------------------------------------
248 def define_state_vars(self):
249 """Define state vars."""
250 return {
251 "current" : self.current,
252 "voltage": self.voltage
253 }
256 # -----------------------------------------------------------------------------
257 # Scaling methods
258 def calculate_scaling_factors(self):
259 super().calculate_scaling_factors()
260 # This doesn't do anything, but it's a good example of how to get and set scaling factors in relation to each other.
261 sfc = iscale.get_scaling_factor(self.current)
262 iscale.set_scaling_factor(self.current, sfc)
263 sfv = iscale.get_scaling_factor(self.voltage)
264 iscale.set_scaling_factor(self.voltage, sfv)