Coverage for backend/idaes_service/solver/custom/energy/hydro.py: 49%

41 statements  

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

1# Import Pyomo libraries 

2from pyomo.environ import ( 

3 Var, 

4 Suffix, 

5 units as pyunits, 

6) 

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

8 

9# Import IDAES cores 

10from idaes.core import ( 

11 declare_process_block_class, 

12 UnitModelBlockData, 

13 useDefault, 

14) 

15from idaes.core.util.config import is_physical_parameter_block 

16import idaes.core.util.scaling as iscale 

17import idaes.logger as idaeslog 

18 

19from idaes.core.util.tables import create_stream_table_dataframe 

20 

21from idaes.core.util.exceptions import ConfigurationError 

22 

23# Set up logger 

24_log = idaeslog.getLogger(__name__) 

25 

26 

27# When using this file the name "Solar" is what is imported 

28@declare_process_block_class("Hydro") 

29class HydroData(UnitModelBlockData): 

30 """ 

31 Zero order Link model 

32 """ 

33 

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

35 CONFIG = ConfigBlock() 

36 

37 CONFIG.declare( 

38 "dynamic", 

39 ConfigValue( 

40 domain=In([False]), 

41 default=False, 

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

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

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

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

46 ), 

47 ) 

48 CONFIG.declare( 

49 "has_holdup", 

50 ConfigValue( 

51 default=False, 

52 domain=In([False]), 

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

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

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

56 this must be False.""", 

57 ), 

58 ) 

59 CONFIG.declare( 

60 "property_package", 

61 ConfigValue( 

62 default=useDefault, 

63 domain=is_physical_parameter_block, 

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

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

66 **default** - useDefault. 

67 **Valid values:** { 

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

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

70 ), 

71 ) 

72 CONFIG.declare( 

73 "property_package_args", 

74 ConfigBlock( 

75 implicit=True, 

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

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

78 and used when constructing these, 

79 **default** - None. 

80 **Valid values:** { 

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

82 ), 

83 ) 

84 

85 def build(self): 

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

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

88 super().build() 

89 

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

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

92 

93 

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

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

96 # Add inlet block 

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

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

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

100 

101 # Add outlet 

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

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

104 self.flowsheet().config.time, 

105 doc="Material properties of outlet", 

106 **tmp_dict 

107 ) 

108 

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

110 

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

112 

113 # Add variable for efficiency 

114 self.efficiency = Var( 

115 initialize=1.0, 

116 doc="Efficiency", 

117 ) 

118 

119 # Add variable for flow rate of water m3/s 

120 self.flow_rate = Var(self.flowsheet().config.time, 

121 initialize=1.0, units = pyunits.m**3/pyunits.s, 

122 doc="Flow rate of water", 

123 ) 

124 

125 # Add variable for Static Head in meters 

126 self.static_head = Var( 

127 initialize=1.0, units = pyunits.m, 

128 doc="Effective Head or height of the water column", 

129 ) 

130 # Add variable for Cp which is density of water in kg/m3 

131 self.Cp= 1000 

132 # Add variable for Cg which is gravitational acceleration in m/s2 

133 self.Cg= 9.81 

134 

135 # Add constraints 

136 @self.Constraint( 

137 self.flowsheet().time, 

138 doc="Power usage", 

139 ) 

140 

141 def eq_power_balance(b, t): 

142 return ( 

143 (self.flow_rate[t] * self.Cg * self.Cp * self.efficiency * self.static_head == b.properties_out[t].power) 

144 ) 

145 

146 

147 def calculate_scaling_factors(self): 

148 super().calculate_scaling_factors() 

149 

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

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

152 pass 

153 

154 def _get_stream_table_contents(self, time_point=0): 

155 """ 

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

157 

158 Developers should overload this as appropriate. 

159 """ 

160 try: 

161 return create_stream_table_dataframe( 

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

163 ) 

164 except AttributeError: 

165 raise ConfigurationError( 

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

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

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

169 )