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

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

58 

59 def build(self): 

60 """ 

61 Callable method for Block construction. 

62 """ 

63 super(transformerParameterData, self).build() 

64 

65 self._state_block_class = transformerStateBlock 

66 

67 # Variables 

68 self.current = Var(initialize=0, domain=Reals, units=pyunits.A) 

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

70 

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) 

75 

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 ) 

92 

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. 

98 

99 Returns: 

100 StateBlock 

101 

102 """ 

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

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

105 

106 if initialize == {}: 

107 kwargs["parameters"] = self 

108 else: 

109 for i in initialize.keys(): 

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

111 

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

113 *args, **kwargs, initialize=initialize 

114 ) 

115 

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 ) 

126 

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: 

147 

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 

152 

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

180 

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 ) 

197 

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) 

204 

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

206 """ 

207 Method to release state variables fixed during initialisation. 

208 

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 

223 

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

230 

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

232 

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 ) 

245 

246 # ----------------------------------------------------------------------------- 

247 

248 def define_state_vars(self): 

249 """Define state vars.""" 

250 return { 

251 "current" : self.current, 

252 "voltage": self.voltage 

253 } 

254 

255 

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) 

265 

266 

267