Coverage for backend/idaes_service/solver/custom/energy/ac_property_package.py: 41%

80 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 "acParameterBlock" is what is imported 

49@declare_process_block_class("acParameterBlock") 

50class acParameterData(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(acParameterData, self).build() 

64 

65 self._state_block_class = acStateBlock 

66 

67 # Variables 

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

69 self.reactive_power = Var(initialize=0, domain=Reals, units=pyunits.W) 

70 self.voltage = Var(initialize=0, domain=Reals, units=pyunits.V) 

71 

72 # Default scaling values should be provided so that our tools can ensure the model is well-scaled 

73 # Generally scaling factors should be such that if it is multiplied by the variable it will range between 0.01 and 100 

74 self.set_default_scaling("active_power", 1e-3) 

75 self.set_default_scaling("reactive_power", 1e-3) 

76 self.set_default_scaling("voltage", 1e-3) 

77 

78 @classmethod 

79 def define_metadata(cls, obj): 

80 # see https://github.com/watertap-org/watertap/blob/main/tutorials/creating_a_simple_property_model.ipynb 

81 obj.add_properties( 

82 { 

83 "active_power": {"method": None}, 

84 "reactive_power": {"method": None}, 

85 "voltage": {"method": None}, 

86 } 

87 ) 

88 obj.add_default_units( 

89 { 

90 "time": pyunits.s, 

91 "length": pyunits.m, 

92 "mass": pyunits.kg, 

93 } 

94 ) 

95 

96 def build_state_block(self, *args, **kwargs): 

97 """ 

98 Methods to construct a StateBlock associated with this 

99 PhysicalParameterBlock. This will automatically set the parameters 

100 construction argument for the StateBlock. 

101 

102 Returns: 

103 StateBlock 

104 

105 """ 

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

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

108 

109 if initialize == {}: 

110 kwargs["parameters"] = self 

111 else: 

112 for i in initialize.keys(): 

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

114 

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

116 *args, **kwargs, initialize=initialize 

117 ) 

118 

119 @property 

120 def state_block_class(self): 

121 if self._state_block_class is not None: 

122 return self._state_block_class 

123 else: 

124 raise AttributeError( 

125 "{} has not assigned a StateBlock class to be associated " 

126 "with this property package. Please contact the developer of " 

127 "the property package.".format(self.name) 

128 ) 

129 

130# STEP 3: State Block 

131class _acStateBlock(StateBlock): 

132 def initialize( 

133 self, 

134 state_args=None, 

135 state_vars_fixed=False, 

136 hold_state=False, 

137 outlvl=idaeslog.NOTSET, 

138 solver=None, 

139 optarg=None, 

140 ): 

141 """ 

142 Initialization routine for property package. 

143 Keyword Arguments: 

144 state_args : Dictionary with initial guesses for the state vars 

145 chosen. Note that if this method is triggered 

146 through the control volume, and if initial guesses 

147 were not provided at the unit model level, the 

148 control volume passes the inlet values as initial 

149 guess.The keys for the state_args dictionary are: 

150 

151 flow_mass_phase_comp : value at which to initialize 

152 phase component flows 

153 pressure : value at which to initialize pressure 

154 temperature : value at which to initialize temperature 

155 

156 state_vars_fixed: Flag to denote if state vars have already been 

157 fixed. 

158 - True - states have already been fixed by the 

159 control volume 1D. Control volume 0D 

160 does not fix the state vars, so will 

161 be False if this state block is used 

162 with 0D blocks. 

163 - False - states have not been fixed. The state 

164 block will deal with fixing/unfixing. 

165 hold_state : flag indicating whether the initialization routine 

166 should unfix any state variables fixed during 

167 initialization (default=False). 

168 - True - states variables are not unfixed, and 

169 a dict of returned containing flags for 

170 which states were fixed during 

171 initialization. 

172 - False - state variables are unfixed after 

173 initialization by calling the 

174 release_state method 

175 outlvl : sets output level of initialization routine (default=idaeslog.NOTSET) 

176 solver : Solver object to use during initialization if None is provided 

177 it will use the default solver for IDAES (default = None) 

178 optarg : solver options dictionary object (default=None) 

179 Returns: 

180 If hold_states is True, returns a dict containing flags for 

181 which states were fixed during initialization. 

182 """ 

183 

184 # Fix state variables 

185 flags = fix_state_vars(self, state_args) 

186 # Check that dof = 0 when state variables are fixed 

187 for k in self.keys(): 

188 dof = degrees_of_freedom(self[k]) 

189 if dof != 0: 

190 raise PropertyPackageError( 

191 "\nWhile initializing {sb_name}, the degrees of freedom " 

192 "are {dof}, when zero is required. \nInitialization assumes " 

193 "that the state variables should be fixed and that no other " 

194 "variables are fixed. \nIf other properties have a " 

195 "predetermined value, use the calculate_state method " 

196 "before using initialize to determine the values for " 

197 "the state variables and avoid fixing the property variables." 

198 "".format(sb_name=self.name, dof=dof) 

199 ) 

200 

201 # If input block, return flags, else release state 

202 if state_vars_fixed is False: 

203 if hold_state is True: 

204 return flags 

205 else: 

206 self.release_state(flags) 

207 

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

209 """ 

210 Method to release state variables fixed during initialisation. 

211 

212 Keyword Arguments: 

213 flags : dict containing information of which state variables 

214 were fixed during initialization, and should now be 

215 unfixed. This dict is returned by initialize if 

216 hold_state=True. 

217 outlvl : sets output level of of logging 

218 """ 

219 if flags is None: 

220 return 

221 # Unfix state variables 

222 for attr in flags: 

223 if flags[attr] is True: 

224 getattr(self, attr).unfix() 

225 return 

226 

227# STEP 4:  

228@declare_process_block_class("acStateBlock", block_class=_acStateBlock) 

229class acStateBlockData(StateBlockData): 

230 def build(self): 

231 """Callable method for Block construction.""" 

232 super(acStateBlockData, self).build() 

233 

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

235 

236 self.active_power = Var( 

237 initialize=0, 

238 domain=Reals, 

239 units=pyunits.W, 

240 doc="active power flow", 

241 ) 

242 self.reactive_power = Var( 

243 initialize=0, 

244 domain=Reals, 

245 units=pyunits.W, 

246 doc="reactive power flow", 

247 ) 

248 self.voltage = Var( 

249 initialize=0, 

250 domain=Reals, 

251 units=pyunits.V, 

252 doc="voltage", 

253 ) 

254 

255 # ----------------------------------------------------------------------------- 

256 

257 def define_state_vars(self): 

258 """Define state vars.""" 

259 return { 

260 "active_power": self.active_power, 

261 "reactive_power" : self.reactive_power, 

262 "voltage": self.voltage 

263 } 

264 

265 

266 # ----------------------------------------------------------------------------- 

267 # Scaling methods 

268 def calculate_scaling_factors(self): 

269 super().calculate_scaling_factors() 

270 # This doesn't do anything, but it's a good example of how to get and set scaling factors in relation to each other. 

271 sfa = iscale.get_scaling_factor(self.active_power) 

272 iscale.set_scaling_factor(self.active_power, sfa) 

273 sfr = iscale.get_scaling_factor(self.reactive_power) 

274 iscale.set_scaling_factor(self.reactive_power, sfr) 

275 sfv = iscale.get_scaling_factor(self.active_power) 

276 iscale.set_scaling_factor(self.voltage, sfv) 

277 

278 

279