Coverage for backend/idaes_service/solver/custom/energy/energy_splitter.py: 28%

68 statements  

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

1# Import Pyomo libraries 

2from stringprep import in_table_a1 

3from pyomo.environ import ( 

4 Var, 

5 Suffix, 

6 units as pyunits, 

7 Set, 

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

30@declare_process_block_class("EnergySplitter") 

31class EnergySplitterData(UnitModelBlockData): 

32 """ 

33 Zero order energy splitter 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 CONFIG.declare( 

87 "num_inlets", 

88 ConfigValue( 

89 default=False, 

90 domain=int, 

91 description="Number of inlets to add", 

92 doc="""Number of inlets to add""", 

93 ), 

94 ) 

95 CONFIG.declare( 

96 "num_outlets", 

97 ConfigValue( 

98 default=False, 

99 domain=int, 

100 description="Number of outlets to add", 

101 doc="""Number of outlets to add""", 

102 ), 

103 ) 

104 

105 def build(self): 

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

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

108 super().build() 

109 

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

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

112 

113 

114 # Defining parameters of state block class 

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

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

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

118 

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

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

121 num_inlets = self.config.num_inlets 

122 

123 self.inlet_list = [ "inlet_" + str(i+1) for i in range(num_inlets) ] 

124 

125 

126 self.inlet_blocks = [] 

127 for name in self.inlet_list: 

128 # add properties_inlet_1, properties_inlet2 etc 

129 state_block = self.config.property_package.state_block_class( 

130 self.flowsheet().config.time, doc="inlet power", **tmp_dict 

131 ) 

132 self.inlet_blocks.append(state_block) 

133 # Dynamic equivalent to self.properties_inlet_1 = stateblock 

134 setattr(self,"properties_" + name, state_block) 

135 # also add the port 

136 self.add_port(name=name,block=state_block) 

137 

138 

139 num_outlets = self.config.num_outlets 

140 self.outlet_list = [ "outlet_" + str(i+1) for i in range(num_outlets) ] 

141 self.outlet_set = Set(initialize=self.outlet_list) 

142 

143 

144 self.outlet_blocks = [] 

145 for name in self.outlet_list: 

146 # add properties_inlet_1, properties_inlet2 etc 

147 state_block = self.config.property_package.state_block_class( 

148 self.flowsheet().config.time, doc="inlet power", **tmp_dict 

149 ) 

150 self.outlet_blocks.append(state_block) 

151 # Dynamic equivalent to self.properties_inlet_1 = stateblock 

152 setattr(self,"properties_" + name, state_block) 

153 # also add the port 

154 self.add_port(name=name,block=state_block) 

155 

156 

157 #Add variable for capacity: 

158 self.capacity = Var( 

159 self.flowsheet().config.time, 

160 initialize=1.0, 

161 units = pyunits.W, 

162 doc="Capacity of the link", 

163 ) 

164 # Add variable for power splitting 

165 self.split_fraction = Var( 

166 self.flowsheet().time, 

167 self.outlet_set, 

168 initialize=1.0, 

169 #units = pyunits.dimensionless, 

170 doc="How the power is split between outlets", 

171 ) 

172 

173 

174 @self.Expression( 

175 self.flowsheet().time, 

176 ) 

177 def total_power(b, t): 

178 return ( 

179 sum( 

180 state_block[t].power for state_block in self.inlet_blocks 

181 ) 

182 ) 

183 

184 # Add constraints  

185 @self.Constraint( 

186 self.flowsheet().time, 

187 self.outlet_list, 

188 doc="power split", 

189 ) 

190 def eq_power(b,t,o): 

191 outlet_block = getattr(self,"properties_" + o) 

192 return outlet_block[t].power == ( 

193 self.total_power[t] * b.split_fraction[t,o]) 

194 

195 @self.Constraint( 

196 self.flowsheet().time, 

197 doc="Split fraction sum to 1", 

198 ) 

199 def eq_split_fraction_sum(b, t): 

200 return sum(b.split_fraction[t, o] for o in self.outlet_list) == 1.0 

201 

202 

203 def calculate_scaling_factors(self): 

204 super().calculate_scaling_factors() 

205 

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

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

208 for t in blk.flowsheet().time: 

209 for state_block in blk.inlet_blocks: 

210 if(state_block[t].power.value > blk.capacity[t].value): 

211 raise ConfigurationError( 

212 "Danger: Input power exceeds splitter capacity. Please either increase capacity or lower input power." 

213 ) 

214 

215 def _get_stream_table_contents(self, time_point=0): 

216 """ 

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

218 

219 Developers should overload this as appropriate. 

220 """ 

221 

222 io_dict = {} 

223 for inlet_name in self.inlet_list: 

224 io_dict[inlet_name] = getattr(self, inlet_name) # get a reference to the port 

225 

226 io_dict = {} 

227 for outlet_name in self.outlet_list: 

228 io_dict[outlet_name] = getattr(self, outlet_name)