Coverage for backend/django/PinchAnalysis/models/InputModels.py: 86%

127 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-12-18 04:00 +0000

1from django.db import models 

2from core.auxiliary.enums.unitOpGraphics import ConType 

3from core.auxiliary.enums import pinchEnums 

4from core.managers import AccessControlManager 

5 

6from flowsheetInternals.unitops.models.SimulationObject import SimulationObject 

7from core.auxiliary.enums import SimulationObjectClass 

8from core.auxiliary.models.PropertyInfo import PropertyInfo 

9 

10class PinchInputs(models.Model): 

11 flowsheet = models.ForeignKey("core_auxiliary.Flowsheet", on_delete=models.CASCADE, related_name="PinchInputs") 

12 project_owner = models.OneToOneField("PinchAnalysis.StreamDataProject", on_delete=models.CASCADE, related_name="Inputs", null=True) 

13 name = models.CharField(max_length=64, default="Inputs") 

14 

15 created_at = models.DateTimeField(auto_now_add=True) 

16 objects = AccessControlManager() 

17 

18 

19 

20class StreamDataEntry(models.Model): 

21 flowsheet = models.ForeignKey("core_auxiliary.Flowsheet", on_delete=models.CASCADE, related_name="StreamDataEntries") 

22 custom = models.BooleanField(default=False) 

23 group = models.ForeignKey("flowsheetInternals_graphicData.Grouping", on_delete=models.CASCADE, related_name="StreamDataEntries") 

24 streamDataProject = models.ForeignKey("StreamDataProject", on_delete=models.CASCADE, related_name="StreamDataEntries") 

25 unitop = models.ForeignKey("flowsheetInternals_unitops.SimulationObject", on_delete=models.CASCADE, related_name="StreamDataEntries", null=True) 

26 property_package_mapping = models.CharField(max_length=32, null=True) # eg: "Cold Side" 

27 # nextStream = models.ManyToManyField("StreamDataEntry", blank=True) 

28 t_h_data = models.JSONField(null=True, blank=True) 

29 states = models.JSONField(null=True, blank=True) 

30 expanded = models.BooleanField(default=False) 

31 

32 Segments: models.QuerySet["Segment"] 

33 

34 created_at = models.DateTimeField(auto_now_add=True) 

35 objects = AccessControlManager() 

36 

37 @property 

38 def zone(self) -> str: 

39 return self.group.simulationObject.componentName 

40 

41 # TODO: Null checks are really bad on these, if we have a custom StreamDataEntry, these would break. 

42 # We can use the first and last segment to get the inlet and outlet streams, but thats for the future 

43 @property 

44 def inlet_outlet_stream(self) -> tuple[SimulationObject, SimulationObject]: 

45 unitop_schema = self.unitop.schema 

46 property_package_ports = unitop_schema.propertyPackagePorts[self.property_package_mapping] 

47 # We can use get here because Pinch only worries about heaters and heat exchangers, which only 

48 # have one inlet and one outlet for each property package. 

49 inletPort = self.unitop.ports.get(key__in=property_package_ports, direction=ConType.Inlet) 

50 inletStream = inletPort.stream 

51 

52 outletPort = self.unitop.ports.get(key__in=property_package_ports, direction=ConType.Outlet) 

53 outletStream = outletPort.stream 

54 return (inletStream, outletStream) 

55 

56 @property 

57 def temperatures(self) -> tuple[PropertyInfo, PropertyInfo]: 

58 inlet, outlet = self.inlet_outlet_stream 

59 inlet_temp = inlet.properties.get_property("temperature") 

60 outlet_temp = outlet.properties.get_property("temperature") 

61 return (inlet_temp, outlet_temp) 

62 

63 @property 

64 def pressures(self) -> tuple[PropertyInfo, PropertyInfo]: 

65 inlet, outlet = self.inlet_outlet_stream 

66 inlet_pressure = inlet.properties.get_property("pressure") 

67 outlet_pressure = outlet.properties.get_property("pressure") 

68 return (inlet_pressure, outlet_pressure) 

69 

70 @property 

71 def enthalpies(self) -> tuple[PropertyInfo, PropertyInfo]: 

72 inlet, outlet = self.inlet_outlet_stream 

73 inlet_enthalpy = inlet.properties.get_property("enth_mol") 

74 outlet_enthalpy = outlet.properties.get_property("enth_mol") 

75 return (inlet_enthalpy, outlet_enthalpy) 

76 

77 @property 

78 def heat_flow(self) -> PropertyInfo: 

79 match self.unitop.objectType: 

80 case SimulationObjectClass.HeatExchanger: 80 ↛ 81line 80 didn't jump to line 81 because the pattern on line 80 never matched

81 heat_flow = self.unitop.properties.get_property("heat_duty") 

82 case SimulationObjectClass.Heater: 82 ↛ 84line 82 didn't jump to line 84 because the pattern on line 82 always matched

83 heat_flow = self.unitop.properties.get_property("heat_duty") 

84 case SimulationObjectClass.Cooler: 

85 heat_flow = self.unitop.properties.get_property("heat_duty_inverted") 

86 case _: 

87 raise ValueError("Invalid simulation object type for heat flow extraction.") 

88 return heat_flow 

89 

90class Segment(models.Model): 

91 flowsheet = models.ForeignKey("core_auxiliary.Flowsheet", on_delete=models.CASCADE, related_name="Segments") 

92 stream_data_entry = models.ForeignKey(StreamDataEntry, on_delete=models.CASCADE, related_name="Segments", null=True) 

93 hen_node = models.ForeignKey("HenNode", on_delete=models.CASCADE, related_name="Segments", null=True) 

94 name = models.CharField(max_length=128) 

95 t_supply = models.FloatField(default=0) 

96 t_target = models.FloatField(default=0) 

97 p_supply = models.FloatField(default=0) 

98 p_target = models.FloatField(default=0) 

99 h_supply = models.FloatField(default=0) 

100 h_target = models.FloatField(default=0) 

101 heat_flow = models.FloatField(default=0) 

102 dt_cont = models.FloatField(default=5.0) 

103 htc = models.FloatField(default=1.0) 

104 area = models.FloatField(default=0) 

105 

106 created_at = models.DateTimeField(auto_now_add=True) 

107 objects = AccessControlManager() 

108 

109 @property 

110 def zone(self) -> str: 

111 return self.zone_object.simulationObject.componentName 

112 

113 @property 

114 def zone_object(self): 

115 return self.stream_data_entry.group 

116 

117 

118 class Meta: 

119 ordering = ['created_at'] 

120 

121 @classmethod 

122 def create(cls, **kwargs): 

123 instance = cls.objects.create(**kwargs) 

124 instance.save() 

125 return instance 

126 

127 def _calc_area(self) -> float: 

128 """ 

129 A = Q / (htc * deltaT) 

130 Q:kW 

131 htc: kw/m^2/degC 

132 deltaT: t_supply - t_target in degC 

133 THIS IS WRONG AND NEEDS REVISION (dT is wrong). 

134 Needs ln((dT_HOT)/(dT_COLD)) 

135 """ 

136 htc = float(self.htc or 1) 

137 Q = float(self.heat_flow or 0) 

138 dT = abs(float(self.t_supply) - float(self.t_target)) 

139 if htc <= 0 or dT <=0: 139 ↛ 140line 139 didn't jump to line 140 because the condition on line 139 was never true

140 return 0.0 

141 return Q / (htc * dT) 

142 

143 

144class PinchUtility(models.Model): 

145 flowsheet = models.ForeignKey("core_auxiliary.Flowsheet", on_delete=models.CASCADE, related_name="PinchUtilities") 

146 input_owner = models.ForeignKey(PinchInputs, on_delete=models.CASCADE, related_name="PinchUtilities", null=False) 

147 # zone = models.CharField(max_length=32, null=True) ############ ADD ################### 

148 name = models.CharField(max_length=64) 

149 type = models.CharField(choices=pinchEnums.StreamType.choices , default=pinchEnums.StreamType.Both) 

150 t_supply = models.FloatField(null=True) 

151 t_target = models.FloatField(null=True) 

152 heat_flow = models.FloatField(default=0, null=True) ############ CHANGE TO MAX HEAT FLOW ################### 

153 dt_cont = models.FloatField(default=5.0, null=True) 

154 price = models.FloatField(default=30.0, null=True) 

155 htc = models.FloatField(default=1.0, null=True) 

156 

157 created_at = models.DateTimeField(auto_now_add=True) 

158 objects = AccessControlManager() 

159 

160 

161 class Meta: 

162 ordering = ['created_at'] 

163 

164 @classmethod 

165 def create(cls, **kwargs): 

166 # Default stream values 

167 defaults = { 

168 'input_owner': kwargs.get('input_owner', None), 

169 # 'zone': kwargs.get('zone', "Site"), 

170 'name': kwargs.get('name', "Utility"), 

171 'type': kwargs.get('type', pinchEnums.StreamType.Both), 

172 't_supply': kwargs.get('t_supply', None), 

173 't_target': kwargs.get('t_target', None), 

174 'heat_flow': kwargs.get('heat_flow', 0.0), 

175 'dt_cont': kwargs.get('dt_cont', 5.0), 

176 'htc': kwargs.get('htc', 1.0), 

177 'price': kwargs.get('price', 1.0), 

178 } 

179 

180 kwargs.update(defaults) 

181 instance = cls.objects.create(**kwargs) 

182 instance.save() 

183 return instance