Coverage for backend/idaes_service/solver/custom/energy/solar.py: 88%

40 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 

19 

20from idaes.core.util.tables import create_stream_table_dataframe 

21 

22from idaes.core.util.exceptions import ConfigurationError 

23 

24 

25 

26# Set up logger 

27_log = idaeslog.getLogger(__name__) 

28 

29 

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

31@declare_process_block_class("Solar") 

32class SolarData(UnitModelBlockData): 

33 """ 

34 Zero order Link model 

35 """ 

36 

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

38 CONFIG = ConfigBlock() 

39 

40 CONFIG.declare( 

41 "dynamic", 

42 ConfigValue( 

43 domain=In([False]), 

44 default=False, 

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

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

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

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

49 ), 

50 ) 

51 CONFIG.declare( 

52 "has_holdup", 

53 ConfigValue( 

54 default=False, 

55 domain=In([False]), 

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

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

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

59 this must be False.""", 

60 ), 

61 ) 

62 CONFIG.declare( 

63 "property_package", 

64 ConfigValue( 

65 default=useDefault, 

66 domain=is_physical_parameter_block, 

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

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

69 **default** - useDefault. 

70 **Valid values:** { 

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

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

73 ), 

74 ) 

75 CONFIG.declare( 

76 "property_package_args", 

77 ConfigBlock( 

78 implicit=True, 

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

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

81 and used when constructing these, 

82 **default** - None. 

83 **Valid values:** { 

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

85 ), 

86 ) 

87 

88 

89 

90 def build(self): 

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

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

93 super().build() 

94 

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

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

97 

98 

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

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

101 # Add inlet block 

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

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

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

105 

106 # Add outlet 

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

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

109 self.flowsheet().config.time, 

110 doc="Material properties of outlet", 

111 **tmp_dict 

112 ) 

113 

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

115 

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

117 

118 # Pyomo variables: 

119 

120 # Add variable for efficiency 

121 self.efficiency = Var( 

122 initialize=1.0, 

123 doc="Efficiency of the panel", 

124 ) 

125 

126 # Add variable for solar irradiation 

127 self.irradiation = Var(self.flowsheet().config.time, 

128 initialize=1.0, units = pyunits.W/pyunits.m**2, 

129 doc="Amount of sunlight hitting the panel", 

130 ) 

131 

132 # Add variable for area 

133 self.area = Var( 

134 initialize=1.0, units = pyunits.m**2, 

135 doc="Size of the panel", 

136 ) 

137 

138 # Add variable for no. of solar panels 

139 self.panel_count = Var( 

140 initialize=1.0, 

141 doc="Number of solar panels", 

142 ) 

143 

144 # Add constraints 

145 @self.Constraint( 

146 self.flowsheet().time, 

147 doc="Power usage", 

148 ) 

149 def eq_power_balance(b, t): 

150 return ( 

151 self.irradiation[t] * self.efficiency * self.area * self.panel_count == b.properties_out[t].power 

152 ) 

153 

154 def calculate_scaling_factors(self): 

155 super().calculate_scaling_factors() 

156 

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

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

159 pass 

160 

161 def _get_stream_table_contents(self, time_point=0): 

162 """ 

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

164 

165 Developers should overload this as appropriate. 

166 """ 

167 try: 

168 return create_stream_table_dataframe( 

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

170 ) 

171 except AttributeError: 

172 raise ConfigurationError( 

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

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

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

176 )