Coverage for backend/idaes_service/solver/custom/energy/energy_mixer.py: 84%

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

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

9 

10# Import IDAES cores 

11from idaes.core import ( 

12 declare_process_block_class, 

13 UnitModelBlockData, 

14 useDefault, 

15) 

16from idaes.core.util.config import is_physical_parameter_block 

17import idaes.core.util.scaling as iscale 

18import idaes.logger as idaeslog 

19 

20from idaes.core.util.tables import create_stream_table_dataframe 

21 

22from idaes.core.util.exceptions import ConfigurationError, BurntToast 

23 

24# Set up logger 

25_log = idaeslog.getLogger(__name__) 

26 

27 

28# When using this file the name "EnergyMixer" is what is imported 

29@declare_process_block_class("EnergyMixer") 

30class EnergyMixerData(UnitModelBlockData): 

31 """ 

32 Zero order energy_mixer model 

33 """ 

34 

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

36 CONFIG = ConfigBlock() 

37 

38 CONFIG.declare( 

39 "dynamic", 

40 ConfigValue( 

41 domain=In([False]), 

42 default=False, 

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

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

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

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

47 ), 

48 ) 

49 CONFIG.declare( 

50 "has_holdup", 

51 ConfigValue( 

52 default=False, 

53 domain=In([False]), 

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

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

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

57 this must be False.""", 

58 ), 

59 ) 

60 CONFIG.declare( 

61 "property_package", 

62 ConfigValue( 

63 default=useDefault, 

64 domain=is_physical_parameter_block, 

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

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

67 **default** - useDefault. 

68 **Valid values:** { 

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

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

71 ), 

72 ) 

73 CONFIG.declare( 

74 "property_package_args", 

75 ConfigBlock( 

76 implicit=True, 

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

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

79 and used when constructing these, 

80 **default** - None. 

81 **Valid values:** { 

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

83 ), 

84 ) 

85 CONFIG.declare( 

86 "num_inlets", 

87 ConfigValue( 

88 default=False, 

89 domain=int, 

90 description="Number of inlets to add", 

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

92 ), 

93 ) 

94 

95 def build(self): 

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

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

98 super().build() 

99 

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

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

102 

103 

104 # Defining parameters of state block class 

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

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

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

108 

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

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

111 num_inlets = self.config.num_inlets 

112 

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

114 

115 

116 self.inlet_blocks = [] 

117 for name in self.inlet_list: 

118 # add properties_inlet_1, properties_inlet2 etc 

119 state_block = self.config.property_package.state_block_class( 

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

121 ) 

122 self.inlet_blocks.append(state_block) 

123 # Dynamic equivalent to self.properties_inlet_1 = stateblock 

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

125 # also add the port 

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

127 

128 

129 # Add outlet state block 

130 tmp_dict["defined_state"] = False # outlet and waste block is not an inlet 

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

132 self.flowsheet().config.time, 

133 doc="Material properties of outlet", 

134 **tmp_dict 

135 ) 

136 

137 # Add outlet port 

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

139 

140 #Add variables for capacity and efficiency: 

141 self.efficiency = Var(self.flowsheet().config.time, 

142 initialize=1.0, 

143 doc="Efficiency of the link", 

144 ) 

145 self.capacity = Var( 

146 initialize=1.0, 

147 units = pyunits.W, 

148 doc="Capacity of the link", 

149 ) 

150 

151 

152 # Add constraints 

153 # Usually unit models use a control volume to do the mass, energy, and momentum 

154 # balances, however, they will be explicitly written out in this example 

155 

156 @self.Constraint( 

157 self.flowsheet().time, 

158 doc="Power usage", 

159 ) 

160 def eq_power_balance(b, t): 

161 return ( 

162 sum( 

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

164 ) * self.efficiency[t] 

165 == b.properties_out[t].power 

166 ) 

167 

168 def calculate_scaling_factors(self): 

169 super().calculate_scaling_factors() 

170 

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

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

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

174 power_in = 0 

175 for state_block in blk.inlet_blocks: 

176 power_in += state_block[t].power.value 

177 if not blk.properties_out[t].power.fixed: 177 ↛ 179line 177 didn't jump to line 179 because the condition on line 177 was always true

178 blk.properties_out[t].power = power_in 

179 if(power_in > blk.capacity.value): 179 ↛ 180line 179 didn't jump to line 180 because the condition on line 179 was never true

180 raise BurntToast( 

181 "Danger: Input power exceeds energy mixer capacity. Please either increase capacity or lower input power.".format(blk.name) 

182 ) 

183 def _get_stream_table_contents(self, time_point=0): 

184 """ 

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

186 

187 Developers should overload this as appropriate. 

188 """ 

189 

190 io_dict = {} 

191 for inlet_name in self.inlet_list: 

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

193 

194 io_dict["Outlet"] = self.outlet 

195 return create_stream_table_dataframe(io_dict, time_point=time_point)