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

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 

13 

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 

41 

42# Set up logger 

43_log = idaeslog.getLogger(__name__) 

44 

45 

46# STEP 2 

47 

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 ) 

58 

59 def build(self): 

60 """ 

61 Callable method for Block construction. 

62 """ 

63 super(PowerParameterData, self).build() 

64 

65 self._state_block_class = PowerStateBlock 

66 

67 # Variables 

68 self.power = Var(initialize=0, domain=Reals, units=pyunits.W) 

69 

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) 

73 

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 ) 

89 

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. 

95 

96 Returns: 

97 StateBlock 

98 

99 """ 

100 # default = kwargs.pop("default", {}) 

101 initialize = kwargs.pop("initialize", {}) 

102 

103 if initialize == {}: 

104 kwargs["parameters"] = self 

105 else: 

106 for i in initialize.keys(): 

107 initialize[i]["parameters"] = self 

108 

109 return self.state_block_class( # pylint: disable=not-callable 

110 *args, **kwargs, initialize=initialize 

111 ) 

112 

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 ) 

123 

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: 

144 

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 

149 

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 """ 

177 

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 ) 

194 

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) 

201 

202 def release_state(self, flags, outlvl=idaeslog.NOTSET): 

203 """ 

204 Method to release state variables fixed during initialisation. 

205 

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 

220 

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() 

227 

228 self.scaling_factor = Suffix(direction=Suffix.EXPORT) 

229 

230 self.power = Var( 

231 initialize=0, 

232 domain=Reals, 

233 units=pyunits.W, 

234 doc="Power flow", 

235 ) 

236 

237 # ----------------------------------------------------------------------------- 

238 

239 def define_state_vars(self): 

240 """Define state vars.""" 

241 return { 

242 "power": self.power, 

243 } 

244 

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) 

252 

253