Coverage for backend/idaes_service/solver/custom/energy/load.py: 44%

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 

8from idaes.core.util.tables import create_stream_table_dataframe 

9from idaes.core.util.exceptions import ConfigurationError 

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 

20# Set up logger 

21_log = idaeslog.getLogger(__name__) 

22 

23 

24# When using this file the name "Load" is what is imported 

25@declare_process_block_class("Load") 

26class loadData(UnitModelBlockData): 

27 """ 

28 Zero order Load model 

29 """ 

30 

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

32 CONFIG = ConfigBlock() 

33 

34 CONFIG.declare( 

35 "dynamic", 

36 ConfigValue( 

37 domain=In([False]), 

38 default=False, 

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

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

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

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

43 ), 

44 ) 

45 CONFIG.declare( 

46 "has_holdup", 

47 ConfigValue( 

48 default=False, 

49 domain=In([False]), 

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

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

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

53 this must be False.""", 

54 ), 

55 ) 

56 CONFIG.declare( 

57 "property_package", 

58 ConfigValue( 

59 default=useDefault, 

60 domain=is_physical_parameter_block, 

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

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

63 **default** - useDefault. 

64 **Valid values:** { 

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

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

67 ), 

68 ) 

69 CONFIG.declare( 

70 "property_package_args", 

71 ConfigBlock( 

72 implicit=True, 

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

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

75 and used when constructing these, 

76 **default** - None. 

77 **Valid values:** { 

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

79 ), 

80 ) 

81 

82 def build(self): 

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

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

85 super().build() 

86 

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

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

89 

90 

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

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

93 # Add inlet block 

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

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

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

97 self.properties_in = self.config.property_package.state_block_class( 

98 self.flowsheet().config.time, doc="Material properties of inlet", **tmp_dict 

99 ) 

100 # Add outlet and waste block 

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

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

103 self.flowsheet().config.time, 

104 doc="Material properties of outlet", 

105 **tmp_dict 

106 ) 

107 

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

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

110 

111 # Add variables 

112 self.in_power = Var(self.flowsheet().config.time, 

113 initialize=1.0, 

114 doc="Load voltage", 

115 units = pyunits.W 

116 ) 

117 

118 # Add constraints 

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

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

121 @self.Constraint( 

122 self.flowsheet().time, 

123 doc="Power usage", 

124 ) 

125 def eq_power_in_balance(b, t): 

126 return ( 

127 b.properties_out[t].power == self.in_power[t] * -1 

128 ) 

129 

130 

131 def calculate_scaling_factors(self): 

132 super().calculate_scaling_factors() 

133 

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

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

136 for i in blk.properties_in.index_set(): 

137 if not blk.properties_out[i].power.fixed: 

138 blk.properties_out[i].power = blk.properties_in[i].power.value 

139 

140 def _get_stream_table_contents(self, time_point=0): 

141 """ 

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

143 

144 Developers should overload this as appropriate. 

145 """ 

146 try: 

147 return create_stream_table_dataframe( 

148 {"outlet": self.outlet}, time_point=time_point 

149 ) 

150 except AttributeError: 

151 raise ConfigurationError( 

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

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

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

155 )