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
« 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
6from flowsheetInternals.unitops.models.SimulationObject import SimulationObject
7from core.auxiliary.enums import SimulationObjectClass
8from core.auxiliary.models.PropertyInfo import PropertyInfo
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")
15 created_at = models.DateTimeField(auto_now_add=True)
16 objects = AccessControlManager()
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)
32 Segments: models.QuerySet["Segment"]
34 created_at = models.DateTimeField(auto_now_add=True)
35 objects = AccessControlManager()
37 @property
38 def zone(self) -> str:
39 return self.group.simulationObject.componentName
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
52 outletPort = self.unitop.ports.get(key__in=property_package_ports, direction=ConType.Outlet)
53 outletStream = outletPort.stream
54 return (inletStream, outletStream)
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)
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)
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)
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
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)
106 created_at = models.DateTimeField(auto_now_add=True)
107 objects = AccessControlManager()
109 @property
110 def zone(self) -> str:
111 return self.zone_object.simulationObject.componentName
113 @property
114 def zone_object(self):
115 return self.stream_data_entry.group
118 class Meta:
119 ordering = ['created_at']
121 @classmethod
122 def create(cls, **kwargs):
123 instance = cls.objects.create(**kwargs)
124 instance.save()
125 return instance
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)
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)
157 created_at = models.DateTimeField(auto_now_add=True)
158 objects = AccessControlManager()
161 class Meta:
162 ordering = ['created_at']
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 }
180 kwargs.update(defaults)
181 instance = cls.objects.create(**kwargs)
182 instance.save()
183 return instance