Coverage for backend/idaes_service/solver/custom/energy/wind.py: 43%

51 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-11-06 23:27 +0000

1# Import Pyomo libraries 

2from ast import Expression 

3from cmath import pi 

4from pyomo.environ import ( 

5 Var, 

6 Suffix, 

7 units as pyunits, 

8) 

9from pyomo.common.config import ConfigBlock, ConfigValue, In 

10 

11# Import IDAES cores 

12from idaes.core import ( 

13 declare_process_block_class, 

14 UnitModelBlockData, 

15 useDefault, 

16) 

17from idaes.core.util.config import is_physical_parameter_block 

18import idaes.core.util.scaling as iscale 

19import idaes.logger as idaeslog 

20 

21from idaes.core.util.tables import create_stream_table_dataframe 

22 

23from idaes.core.util.exceptions import ConfigurationError 

24 

25# Set up logger 

26_log = idaeslog.getLogger(__name__) 

27 

28 

29# When using this file the name "Wind" is what is imported 

30@declare_process_block_class("Wind") 

31class WindData(UnitModelBlockData): 

32 """ 

33 Zero order Link model 

34 """ 

35 

36 # CONFIG are options for the unit model, this simple model only has the mandatory config options 

37 CONFIG = ConfigBlock() 

38 

39 CONFIG.declare( 

40 "dynamic", 

41 ConfigValue( 

42 domain=In([False]), 

43 default=False, 

44 description="Dynamic model flag - must be False", 

45 doc="""Indicates whether this model will be dynamic or not, 

46 **default** = False. The Bus unit does not support dynamic 

47 behavior, thus this must be False.""", 

48 ), 

49 ) 

50 CONFIG.declare( 

51 "has_holdup", 

52 ConfigValue( 

53 default=False, 

54 domain=In([False]), 

55 description="Holdup construction flag - must be False", 

56 doc="""Indicates whether holdup terms should be constructed or not. 

57 **default** - False. The Bus unit does not have defined volume, thus 

58 this must be False.""", 

59 ), 

60 ) 

61 CONFIG.declare( 

62 "property_package", 

63 ConfigValue( 

64 default=useDefault, 

65 domain=is_physical_parameter_block, 

66 description="Property package to use for control volume", 

67 doc="""Property parameter object used to define property calculations, 

68 **default** - useDefault. 

69 **Valid values:** { 

70 **useDefault** - use default package from parent model or flowsheet, 

71 **PhysicalParameterObject** - a PhysicalParameterBlock object.}""", 

72 ), 

73 ) 

74 CONFIG.declare( 

75 "property_package_args", 

76 ConfigBlock( 

77 implicit=True, 

78 description="Arguments to use for constructing property packages", 

79 doc="""A ConfigBlock with arguments to be passed to a property block(s) 

80 and used when constructing these, 

81 **default** - None. 

82 **Valid values:** { 

83 see property package for documentation.}""", 

84 ), 

85 ) 

86 

87 def build(self): 

88 # build always starts by calling super().build() 

89 # This triggers a lot of boilerplate in the background for you 

90 super().build() 

91 

92 # This creates blank scaling factors, which are populated later 

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

94 

95 

96 # Add state blocks for inlet, outlet, and waste 

97 # These include the state variables and any other properties on demand 

98 # Add inlet block 

99 tmp_dict = dict(**self.config.property_package_args) 

100 tmp_dict["parameters"] = self.config.property_package 

101 #tmp_dict["defined_state"] = True # inlet block is an inlet 

102 

103 # Add outlet 

104 tmp_dict["defined_state"] = False # outlet is not an inlet 

105 self.properties_out = self.config.property_package.state_block_class( 

106 self.flowsheet().config.time, 

107 doc="Material properties of outlet", 

108 **tmp_dict 

109 ) 

110 

111 # Add ports - oftentimes users interact with these rather than the state blocks 

112 

113 self.add_port(name="outlet", block=self.properties_out) 

114 

115 # Add variable for efficiency 

116 self.efficiency = Var( 

117 initialize=1.0, 

118 doc="Efficiency of the turbine", 

119 ) 

120 

121 # Add variable for Windspeed 

122 self.windspeed = Var(self.flowsheet().config.time, 

123 initialize=1.0, units = pyunits.m/pyunits.s, 

124 doc="Speed of wind hitting the turbines", 

125 ) 

126 

127 # Add variable for Pressure 

128 self.pressure = Var(self.flowsheet().config.time, 

129 initialize=1.0, units = pyunits.Pa, 

130 doc="Air pressure", 

131 ) 

132 

133 # Add variable for Temperature 

134 self.air_temp = Var(self.flowsheet().config.time, 

135 initialize=1.0, units = pyunits.degK, 

136 doc="Air Temperature", 

137 ) 

138 

139 # Add variable for Humidity 

140 self.humidity = Var(self.flowsheet().config.time, 

141 initialize=1.0, 

142 doc="Air humidity", 

143 ) 

144 

145 # Add variable for rotor diameter 

146 self.rotor_diameter = Var( 

147 initialize=1.0, units = pyunits.m, 

148 doc="Rotor Diameter", 

149 ) 

150 

151 # Add variable for no. of turbines 

152 self.turbine_count = Var( 

153 initialize=1.0, 

154 doc="Number of turbines", 

155 ) 

156 

157 # Simple expressions 

158 self.radius = self.rotor_diameter / 2 

159 self.area = pi * self.radius**2 

160 self.Cp= 0.579 # Power coefficient, typically between 0.4 and 0.6 for wind turbines 

161 

162 

163 # Expression indexed by time 

164 @self.Expression( 

165 self.flowsheet().time 

166 ) 

167 def density(b,t): 

168 return( 

169 (self.pressure[t]/(287.05 * self.air_temp[t])) * (1-0.378*(self.humidity[t])) 

170 ) 

171 

172 

173 # Add constraints 

174 @self.Constraint( 

175 self.flowsheet().time, 

176 doc="Power usage", 

177 ) 

178 def eq_power_balance(b, t): 

179 return ( 

180 0.5 * self.density[t] * self.area * self.windspeed[t]**3 * self.Cp * self.efficiency * self.turbine_count == b.properties_out[t].power 

181 ) 

182 

183 

184 def calculate_scaling_factors(self): 

185 super().calculate_scaling_factors() 

186 

187 def initialize(blk, *args, **kwargs): 

188 # Just propagate the power from inlet to outlet, good simple method of initialization 

189 pass 

190 

191 def _get_stream_table_contents(self, time_point=0): 

192 """ 

193 Assume unit has standard configuration of 1 inlet and 1 outlet. 

194 

195 Developers should overload this as appropriate. 

196 """ 

197 try: 

198 return create_stream_table_dataframe( 

199 {"Outlet": self.outlet}, time_point=time_point 

200 ) 

201 except AttributeError: 

202 raise ConfigurationError( 

203 f"Unit model {self.name} does not have the standard Port " 

204 f"names (inlet and outlet). Please contact the unit model " 

205 f"developer to develop a unit specific stream table." 

206 )