Coverage for backend/pinch_service/OpenPinch/src/analysis/operation_analysis.py: 4%
283 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-06 23:27 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-11-06 23:27 +0000
1import copy
2from typing import Optional
3from ..utils import *
4from ..classes import *
5from .support_methods import *
8# TODO: Refactor this entire file.
10__all__ = ["get_energy_transfer_retrofit_analysis"]
12#######################################################################################################
13# Public API --- TODO
14#######################################################################################################
16def get_energy_transfer_retrofit_analysis(site: Zone):
17 """Calculates the ETD and retrofit targets.
18 """
20 # Prepares variables and arrays
21 ETD = [ [0, 0] for i in range(1 + len(site.subzones) * 3) ]
22 ETD_star = copy.deepcopy(ETD)
24 for z in site.subzones:
25 # Redefine heat exchanger pockets based on detailed ETD retrofit analysis
26 Req_ut = True if z.hot_utility_target + z.cold_utility_target > ZERO else False
27 if site.config.GCC_VERT_CUT_KINK_OPTION and not Req_ut:
28 z.graphs['GCC_etc'] = site.Reshape_GCC_Pockets(z.graphs['PT_star'], z.graphs['GCC_etc'])
29 site.Extract_Pro_ETC(ETD_star, z, z.graphs['PT_star'], Req_ut)
30 site.Extract_Pro_ETC(ETD, z, z.graphs['PT'], Req_ut)
32 site_tit: Zone = site.targets[TargetType.DI.value]
33 PT_TIT = site_tit.graphs['PT']
34 PT_star_TIT = site_tit.graphs['PT_star']
36 # Forms a complete set of temperature intervals
37 T_int_star = site.Compile_ETD_T_int(ETD_star, PT_star_TIT)
38 T_int = site.Compile_ETD_T_int(ETD, PT_TIT)
40 # Expands Heat Cascade Table to be based on the complete set of temperature intervals
41 ETD_star = site.Transpose_ETD_T(ETD_star, T_int_star)
42 ETD = site.Transpose_ETD_T(ETD, T_int)
44 # Calculates the ETD
45 ETD_star_header = site.Stack_ETD(ETD_star, PT_star_TIT, 'ETD', True)
46 ETD_header = site.Stack_ETD(ETD, PT_TIT, 'ETD', False)
48 # Shift thermodynamic limiting curve to match the end of the ETD
49 dh = ETD_star[-1][1] - PT_TIT[10][0]
50 PT_TIT = shift_heat_cascade(PT_TIT, dh, 10)
52 # Determines the Advanced Composite Curve that combines conventional CC and the ETD
53 ACC_star = site.Calc_ACCN(ETD_star, PT_star_TIT)
54 ACC = site.Calc_ACCN(ETD, PT_TIT)
56 # Reduces the number of T int to the minimum
57 site.Simplify_ETD(ETD_star)
58 site.Simplify_ETD(ETD)
59 site.Simplify_ETD(ACC_star)
60 site.Simplify_ETD(ACC)
62 # Record retrofit targets
63 Hot_Pinch, Cold_Pinch = get_pinch_temperatures(PT_star_TIT, 10, 0)
65 Retrofit = Zone(name=TargetType.ET.value, config=site.config)
66 Retrofit.hot_pinch = Hot_Pinch
67 Retrofit.hot_utility_target = ETD_star[-1][1]
68 Retrofit.cold_utility_target = ETD_star[-1][-1]
69 Retrofit.retrofit_target = Retrofit.hot_utility_target - site_tit.hot_utility_target
70 Retrofit.heat_recovery_target = site_tit.heat_recovery_target - Retrofit.retrofit_target
71 Retrofit.degree_of_int = Retrofit.heat_recovery_target / site_tit.heat_recovery_limit if site_tit.heat_recovery_limit > 0 else 1
72 Retrofit.add_graph('ETD', ETD)
73 Retrofit.add_graph('ETD_star', ETD_star)
74 Retrofit.add_graph('ACC', ACC)
75 Retrofit.add_graph('ACC_star', ACC_star)
76 Retrofit.add_graph('ETD_header', ETD_header)
77 site.add_zone(Retrofit)
80#######################################################################################################
81# Helper Functions
82#######################################################################################################
84def Reshape_GCC_Pockets(site, PT_star, GCC_etc):
85 """Redefine GCC pockets based on possible HEN retrofit design considerations.
86 """
87 GCC_etc = GCC_etc[:2]
88 for i in range(len(GCC_etc)):
89 GCC_etc[i] = GCC_etc[i][:len(PT_star[0])]
91 min_H_cross = 1E+35
93 for j in range(len(PT_star[0])):
94 GCC_etc[1][j] = PT_star[1][j]
95 if j == 0 or j == len(PT_star[0]):
96 GCC_etc[2][j] = 0
97 else:
98 if abs(PT_star[9][j]) > ZERO:
99 if PT_star[3][j] > PT_star[6][j]:
100 GCC_etc[2][j] = GCC_etc[2][j - 1] + PT_star[2][j] * PT_star[3][j]
101 else:
102 GCC_etc[2][j] = GCC_etc[2][j - 1] - PT_star[2][j] * PT_star[6][j]
103 else:
104 GCC_etc[2][j] = GCC_etc[2][j - 1]
105 if PT_star[3][j] > ZERO and PT_star[6][j] > ZERO:
106 min_H_cross = min(PT_star[11][j], min_H_cross)
107 min_H_cross = min(PT_star[11][j - 1], min_H_cross)
109 DH_shift = GCC_etc[2][len(GCC_etc[0])]
110 for j in range(len(PT_star[0])):
111 GCC_etc[2][j] = GCC_etc[2][j] - DH_shift
113 for j in range(1, len(PT_star[0])):
114 if min_H_cross > (min(GCC_etc[2][j], GCC_etc[2][j - 1])) + ZERO and min_H_cross < (max(GCC_etc[2][j], GCC_etc[2][j - 1])) - ZERO:
115 j_0 = j
116 h_0 = min_H_cross
117 T_new = linear_interpolation(h_0, GCC_etc[2][j_0], GCC_etc[2][j_0 - 1], GCC_etc[1][j_0], GCC_etc[1][j_0 - 1])
118 PT_star = insert_temperature_interval_into_pt(PT_star, T_new, j_0)
119 j_0 = j
120 GCC_etc = insert_temperature_interval_into_pt(GCC_etc, T_new, j_0)
122 for j in range(len(PT_star[0])):
123 if PT_star[11][j] > min_H_cross:
124 PT_star[11][j] = min_H_cross
125 return GCC_etc
127def Write_HSDT(site, ETD, ETD_header, sheet, row=4, col=1, exclude_small_DH=False):
128 """Prints the ETD table to a spreadsheet.
129 """
130 # DoEvents
131 if not sheet.visible:
132 sheet.visible = True
134 k = 1
135 row_0 = row
136 sheet.cells(1, col).value = 'Ti'
137 # sheet.cells(1, col).characters[1].font.defscript = True
138 sheet.cells(3, col).value = chr(176)
140 for i in range(1, len(ETD), 3):
141 sheet.cells(1, col + k).value = ETD[i][0]
142 sheet.cells(2, col + k).value = chr(916) + 'Hnet'
143 # sheet.cells(2, col + k).characters[2:5].font.defscript = True
144 sheet.cells(3, col + k).value = 'kW'
145 k += 1
147 k = 0
148 for j in range(len(ETD[0])):
149 sheet.cells(row + (j - 1), col + k).value = ETD[0][j]
151 k = 1
152 for i in range(1, len(ETD), 3):
153 for j in range(1, len(ETD[0])):
154 sheet.cells(row + (j - 1), col + k).value = ETD[i][j] if ETD_header[i + 1][5] == 0 or exclude_small_DH == False else 0
155 k += 1
157 # With Range(sheet.cells(row_0, 2), sheet.cells(row_0 + len(ETD[0]) - 1, (len(ETD) - 1) / 3 + 3))
158 # Add conditional formatting to Table
159 # .FormatConditions.AddColorScale ColorScaleType:=3
160 # .FormatConditions(.FormatConditions.count).SetFirstPriority
161 # .FormatConditions(1).ColorScaleCriteria(1).Type = xlConditionValueLowestValue
162 # With .FormatConditions(1).ColorScaleCriteria(1).FormatColor
163 # .ThemeColor = xlThemeColorAccent1
164 # .TintAndShade = 0
165 # End With
166 # .FormatConditions(1).ColorScaleCriteria(2).Type = xlConditionValueNumber
167 # .FormatConditions(1).ColorScaleCriteria(2).Value = 0
168 # With .FormatConditions(1).ColorScaleCriteria(2).FormatColor
169 # .ThemeColor = xlThemeColorDark1
170 # .TintAndShade = 0
171 # End With
172 # .FormatConditions(1).ColorScaleCriteria(3).Type = xlConditionValueHighestValue
173 # With .FormatConditions(1).ColorScaleCriteria(3).FormatColor
174 # .Color = 255
175 # .TintAndShade = 0
176 # End With
178 # Draw boarders for Table
179 # With .Borders(xlEdgeLeft)
180 # .LineStyle = xlContinuous
181 # .ThemeColor = 1
182 # .TintAndShade = -0.149998474074526
183 # .Weight = xlThin
184 # End With
185 # With .Borders(xlEdgeTop)
186 # .LineStyle = xlContinuous
187 # .ThemeColor = 1
188 # .TintAndShade = -0.149998474074526
189 # .Weight = xlThin
190 # End With
191 # With .Borders(xlEdgeBottom)
192 # .LineStyle = xlContinuous
193 # .ThemeColor = 1
194 # .TintAndShade = -0.149998474074526
195 # .Weight = xlThin
196 # End With
197 # With .Borders(xlEdgeRight)
198 # .LineStyle = xlContinuous
199 # .ThemeColor = 1
200 # .TintAndShade = -0.149998474074526
201 # .Weight = xlThin
202 # End With
203 # With .Borders(xlInsideVertical)
204 # .LineStyle = xlContinuous
205 # .ThemeColor = 1
206 # .TintAndShade = -0.149998474074526
207 # .Weight = xlThin
208 # End With
209 # With .Borders(xlInsideHorizontal)
210 # .LineStyle = xlContinuous
211 # .ThemeColor = 1
212 # .TintAndShade = -0.149998474074526
213 # .Weight = xlThin
214 # End With
215 # End With
217def Compile_ETD_T_int(site, ETD, PT_TIT):
218 """Grabs temperatures from every process operation GCC with a defined system,
219 order, and remove duplicates.
220 """
221 T_int = [ [None for i in range(10000)] ]
223 n = 0
224 j = 1
225 while j < len(ETD):
226 for i in range(1, len(ETD[0])):
227 if ETD[j][i] == None:
228 break
229 T_int[0][n] = ETD[j][i]
230 n += 1
231 j += 3
233 j_0 = j + 1
235 for j in range(0, len(PT_TIT), 3):
236 for i in range(len(PT_TIT[0])):
237 if PT_TIT[j][i] == None:
238 break
239 T_int[0][n] = PT_TIT[j][i]
240 n += 1
242 T_int = T_int.sort(reverse=True)
244 # TODO: I think this is unnecessary because the None values would be removed in get_ordered_list.
245 # if T_int[0][len(T_int[0])] < ZERO or T_int[0][len(T_int[0])] == None:
246 # for i in range(len(T_int[0]) - 1, -1, -1):
247 # if T_int[0][i] == None:
248 # break
249 # T_int[0][i] = 0
250 return T_int
252def Transpose_ETD_T(site, ETD, T_int):
253 """Transposes temperature intervals from individual GCC cascades to a common set of temperature intervals for the entire system.
254 """
255 ETD_temp = [ [ None for j in range(len(T_int[0]) + 1)] for i in range(len(ETD))]
257 ETD_temp[0][0] = 'T'
258 for i in range(1, len(ETD_temp[0])):
259 ETD_temp[0][i] = T_int[0][i - 1]
261 for j in range(1, len(ETD), 3):
262 k = 2
263 ETD_temp[j][0] = ETD[j][0]
264 ETD_temp[j + 1][0] = None
265 ETD_temp[j + 2][0] = ETD[j + 2][0]
267 ETD_temp[j][1] = None
268 ETD_temp[j + 1][1] = ETD[j + 2][1]
269 ETD_temp[j + 2][1] = 0
271 for i in range(2, len(ETD_temp[0])):
272 if k >= len(ETD[0]):
273 ETD_temp[j][i] = 0
274 ETD_temp[j + 1][i] = ETD_temp[j + 1][i - 1]
275 continue
276 if (ETD_temp[0][i - 1] <= ETD[j][k - 1] + ZERO) and (ETD_temp[0][i] >= ETD[j][k] - ZERO):
277 CPnet = ETD[j + 1][k]
278 dt = ETD_temp[0][i - 1] - ETD_temp[0][i]
279 ETD_temp[j][i] = dt * CPnet
280 ETD_temp[j + 1][i] = ETD_temp[j + 1][i - 1] + dt * CPnet
281 if abs(ETD_temp[0][i] - ETD[j][k]) < ZERO:
282 k += 1
283 else:
284 ETD_temp[j][i] = 0
285 ETD_temp[j + 1][i] = ETD_temp[j + 1][i - 1]
286 return ETD_temp
288def Simplify_ETD(site, ETD):
289 """Reduces the ETD (and HSDT) to the minimum number of T intervals by removing all
290 intervals between which there are not changes in CP for all process operations.
291 """
292 # Remove low temperature intervals that exceed the maximum temperatures
293 # for i in range(len(ETD[0]) - 1, 1, -1): # Loop from lowerest to highest temperature
294 # for j in range(1, len(ETD), 3):
295 # if ETD[j][i] > ZERO:
296 # break # Temperature interval cannot be removed if true
297 # else:
298 # continue
299 # break
301 # if i < len(ETD[0]):
302 # for j in range(len(ETD)):
303 # ETD[j] = ETD[j][:i]
305 # # Remove high temperature intervals that exceed the maximum temperatures
306 # for i in range(2, len(ETD[0])): # Loop from highest to lowerest temperature
307 # for j in range(1, len(ETD), 3):
308 # if abs(ETD[j][i]) > ZERO:
309 # break # Temperature interval cannot be removed if true
310 # else:
311 # continue
312 # break
314 # i = i - 2
315 # if i > 0:
316 # for n in range(1, len(ETD[0]) - i):
317 # for m in range(len(ETD)):
318 # ETD[m][n] = ETD[m][n + i]
319 # for row in ETD:
320 # row.pop()
322 # Join two T intervals where CP is constant for all zones
323 for i in range(len(ETD[0]) - 1, 2, -1): # Loop from lowest to highest temperature
324 for j in range(1, len(ETD), 3):
325 CP_0 = ETD[j][i] / (ETD[0][i - 1] - ETD[0][i])
326 CP_1 = ETD[j][i - 1] / (ETD[0][i - 2] - ETD[0][i - 1])
327 if abs(CP_0 - CP_1) > ZERO:
328 break # Temperature interval cannot be removed if true
329 else:
330 n = i
331 ETD[0][n - 1] = ETD[0][n]
332 for m in range(1, len(ETD), 3):
333 ETD[m][n - 1] = ETD[m][n - 1] + ETD[m][n]
334 ETD[m + 1][n - 1] = ETD[m + 1][n - 1] + ETD[m][n]
335 ETD[m + 2][n - 1] = ETD[m + 2][n - 1] + ETD[m][n]
336 for n in range(i, len(ETD[0]) - 1):
337 for m in range(len(ETD)):
338 ETD[m][n] = ETD[m][n + 1]
339 for row in ETD:
340 row.pop()
342def Stack_ETD(site, ETD, PT, Diagram_type, shifted):
343 """Determine the order and stack individual heat cascades of process operations.
344 """
345 Hot_Pinch, Cold_Pinch = get_pinch_temperatures(PT, 10, 0)
347 ETD_header = [ [None for j in range(11)] for i in range(len(ETD))]
349 for j in range(1, len(ETD), 3):
350 site.Characterise_ETC(ETD, ETD_header, Hot_Pinch, Cold_Pinch, j + 1, ETD[j + 2][0])
352 # Reorder HX in the ETD
353 ETD_temp = copy.deepcopy(ETD)
355 for j in range(1, len(ETD)):
356 ETD[j] = [None for k in range(len(ETD[0]))]
358 ETD_header_temp = copy.deepcopy(ETD_header)
360 col_ETD = 2
361 site.Write_Next_ETC(ETD, ETD_temp, ETD_header, ETD_header_temp, col_ETD)
363 HX_type_1 = 'C'
364 HX_type_2 = 'R'
365 HX_type_3 = 'H'
367 j_0 = 0
368 for j in range(3, len(ETD), 3):
369 if ETD[j][0] == HX_type_1[:1] or \
370 ETD[j][0] == HX_type_2[:1] or \
371 ETD[j][0] == HX_type_3[:1]:
372 if j_0 == 0:
373 j_0 = j
374 for i in range(1, len(ETD[0])):
375 if j == j_0:
376 ETD[j][i] = 0 if ETD_header[j - 1][5] == 1 and shifted else ETD[j - 1][i]
377 else:
378 ETD[j][i] = ETD[j_0][i] if ETD_header[j - 1][5] == 1 and shifted else ETD[j - 1][i] + ETD[j_0][i]
379 j_0 = j
381 return ETD_header
383def Calc_ACCN(site, ETD, PT_TIT):
384 """Calculate Adv CC with integrated ETD.
385 """
386 ACC = [
387 [ None for j in range(len(ETD[0])) ] for i in range(len(ETD) + 3)
388 ]
390 i = 0
391 ACC[0][0] = ETD[0][0]
392 ACC[1][0] = 'HCC'
394 for j in range(1, len(ETD), 3):
395 ACC[j + 3][0] = ETD[j][0]
396 ACC[j + 4][0] = ETD[j + 1][0]
397 ACC[j + 5][0] = ETD[j + 2][0]
399 for i in range(1, len(ACC[0])):
400 ACC[0][i] = ETD[0][i]
402 if abs(ACC[0][i] - PT_TIT[0][i]) > ZERO:
403 PT_TIT = insert_temperature_interval_into_pt(PT_TIT, ACC[0][i], i)
405 if i > 1:
406 ACC[1][i] = PT_TIT[4][i] - PT_TIT[4][i - 1]
407 ACC[2][i] = PT_TIT[4][i]
408 ACC[3][i] = PT_TIT[4][i]
410 for i in range(1, len(ACC[0])):
411 for j in range(1, len(ETD), 3):
412 ACC[j + 3][i] = ETD[j][i]
413 ACC[j + 4][i] = ETD[j + 1][i]
414 ACC[j + 5][i] = ETD[j + 2][i] + ACC[3][i]
416 return ACC
418def Characterise_ETC(site, ETD, ETD_header, Hot_Pinch, Cold_Pinch, Col_j, HX_mode):
419 """Characterises the shape, enclosed area, and temperature driving force of each heat cascade.
420 """
421 TH_tot_area = 0
422 TH_w_tot_area = 0
423 T_h_max = -1000
424 H_max = 0
425 ETD_header[Col_j][0] = 0 # Check for Cross-Pinch Heat Transfer
426 t_const = 99 # tune weghting constant
427 for i in range(2, len(ETD[0])):
428 TH_sub_area = abs(0.5 * (ETD[Col_j][i - 1] + ETD[Col_j][i]) / (ETD[0][i - 1] - ETD[0][i])) # Determine T-H area (row 1)
429 # Determine weighting factor
430 if ETD[0][i] > Hot_Pinch + ZERO:
431 w = 1 / (abs((ETD[0][i - 1] + ETD[0][i]) / 2 - Hot_Pinch) / t_const + 1)
432 elif ETD[0][i] < Cold_Pinch - ZERO:
433 w = 1 / (abs((ETD[0][i - 1] + ETD[0][i]) / 2 - Cold_Pinch) / t_const + 1)
434 else:
435 w = 1
436 if abs(ETD[Col_j][i]) > ZERO:
437 ETD_header[Col_j][4] = 1
438 TH_tot_area += TH_sub_area
439 TH_w_tot_area += w * TH_sub_area
440 if H_max < abs(ETD[Col_j][i]):
441 H_max = abs(ETD[Col_j][i])
442 if T_h_max == -1000 and abs(ETD[Col_j - 1][i]) > ZERO:
443 T_h_max = (ETD[0][i - 1] + 273.15) if HX_mode == 'C' else 1 / (ETD[0][i - 1] + 273.15)
445 ETD_header[Col_j][0] = ETD[Col_j - 1][0]
446 ETD_header[Col_j][1] = TH_w_tot_area
447 ETD_header[Col_j][2] = TH_tot_area
448 ETD_header[Col_j][3] = H_max
449 ETD_header[Col_j][4] = T_h_max
451 ETD_header[Col_j][10] = 1 / T_h_max if HX_mode == 'C' else T_h_max
453def Write_Next_ETC(site, ETD, ETD_temp, ETD_header, ETD_header_temp, k):
454 H_thres = site.config.THRESHOLD
455 TH_area_thres = site.config.AREA_THRESHOLD * 1000
456 for HX_Type in ['C', 'R', 'H']:
457 HX_num = 0
458 for i in range(3, len(ETD), 3):
459 if ETD_temp[i][0] == HX_Type:
460 HX_num += 1
462 if HX_num > 0:
463 k_max = k + (HX_num - 1) * 3 + 1
464 for k in range(k, k_max, 3):
465 Var_temp = 0
466 for j in range(2, len(ETD_temp), 3):
467 if ETD_header_temp[j][10] > Var_temp and ETD_temp[j + 1][0] == HX_Type:
468 Var_temp = ETD_header_temp[j][10]
469 k_1 = j
470 ETD_header_temp[k_1][10] = 0
471 for i in range(len(ETD_temp[0])):
472 ETD[k - 1][i] = ETD_temp[k_1 - 1][i]
473 ETD[k][i] = ETD_temp[k_1][i]
475 ETD[k + 1][0] = ETD_temp[k_1 + 1][0]
476 for i in range(len(ETD_header_temp[0])):
477 ETD_header[k][i] = ETD_header_temp[k_1][i]
478 ETD_header[k][5] = 0
479 if (site.config.SET_MIN_DH_THRES and ETD_header[k][3] < H_thres) or (site.config.SET_MIN_TH_AREA and ETD_header[k][1] < TH_area_thres):
480 if HX_Type[:1] == 'R': # (RetrofitForm.Excl_UE_Option.Value and (HX_Type[:1] = 'H' or HX_Type[:1] = 'C')) Or
481 ETD_header[k][6] = 1
482 k += 3
484def Extract_Pro_ETC(site, ETD, z, PT, Req_ut):
485 """Save an individual process operation GCC for the ETD.
486 """
487 j = z.zone_num * 3 - 1
488 ETD[j - 1][0] = z.name
489 ETD[j][0] = 'CP net'
490 if Req_ut:
491 ETD[j + 1][0] = 'H' if PT[10][0] > ZERO else 'C'
492 else:
493 ETD[j + 1][0] = 'R'
495 n = max(len(ETD[0]) - 1, len(PT[0])) + 1
496 if n != len(ETD[0]):
497 for i in range(len(ETD)):
498 ETD[i] += [None for k in range(n - len(ETD[i]))]
499 for i in range(len(PT[0])):
500 ETD[j - 1][i + 1] = PT[0][i]
501 ETD[j][i + 1] = PT[8][i]
503 ETD[j + 1][1] = PT[10][0]