Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / auto_parallel / sapp_nd / nd / debug.py: 83%

467 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-07-06 05:41 +0800

1# Copyright 2025 Huawei Technologies Co., Ltd 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14# ============================================================================ 

15"""Debugging utilities""" 

16 

17import os 

18import colorsys 

19import csv 

20from enum import Enum, auto 

21from pathlib import Path 

22from functools import partial 

23from math import isnan, sqrt 

24 

25import pandas as pd 

26import matplotlib.pyplot as plt 

27import matplotlib.colors as mc 

28from matplotlib.font_manager import FontProperties 

29from scipy.stats import pearsonr 

30 

31from hyper_parallel.auto_parallel.sapp_nd.nd.logger import logger 

32import hyper_parallel.auto_parallel.sapp_nd.nd.dimensions as Dim 

33 

34 

35class PerfParts(Enum): 

36 """decomposition of performance""" 

37 

38 FW_COMPUTE = auto() 

39 BW_COMPUTE = auto() 

40 RECOMPUTE = auto() 

41 DP_COMM = auto() 

42 MP_COMM = auto() 

43 EP_COMM = auto() 

44 CP_COMM = auto() 

45 PP_COMM = auto() 

46 BUBBLE = auto() 

47 TOTAL = auto() 

48 MEMORY = auto() 

49 

50 def __str__(self): 

51 return self.name 

52 

53 def short_name(self): 

54 """Returns short component name""" 

55 name = "Perf" 

56 if self == self.FW_COMPUTE: 

57 name = "FW" 

58 elif self == self.BW_COMPUTE: 

59 name = "BW" 

60 elif self == self.RECOMPUTE: 

61 name = "Rec" 

62 elif self == self.DP_COMM: 

63 name = "DP" 

64 elif self == self.MP_COMM: 

65 name = "MP" 

66 elif self == self.EP_COMM: 

67 name = "EP" 

68 elif self == self.CP_COMM: 

69 name = "CP" 

70 elif self == self.PP_COMM: 

71 name = "P2P" 

72 elif self == self.BUBBLE: 

73 name = "BBL" 

74 elif self == self.MEMORY: 

75 name = "MEM" 

76 return name 

77 

78 

79class RealParts(Enum): 

80 """decomposition of performance""" 

81 

82 COMP = auto() 

83 DP_WAIT = auto() 

84 MP_WAIT = auto() 

85 EP_WAIT = auto() 

86 CP_WAIT = auto() 

87 PP_WAIT = auto() 

88 IDLE = auto() 

89 TOTAL = auto() 

90 

91 def __str__(self): 

92 return self.name.lower() 

93 

94 

95class MemParts(Enum): 

96 """decomposition of memory""" 

97 

98 TOTAL = auto() 

99 

100 def __str__(self): 

101 return self.name 

102 

103 

104class Debug: 

105 """Debugging tools""" 

106 

107 def __init__( 

108 self, 

109 parallel_dimensions, 

110 info_type, 

111 enable=True, 

112 output_file="debug.csv", 

113 ): 

114 self.enable = enable 

115 if self.enable: 

116 self.parallel_dimensions = parallel_dimensions 

117 self.info = {p: 0 for p in info_type} 

118 self.output_file = os.path.join( 

119 os.path.dirname(os.path.abspath(__file__)), 

120 "output", 

121 output_file, 

122 ) 

123 def is_enabled(self): 

124 """Check whether debugging is enabled""" 

125 return self.enable 

126 

127 def column_titles(self): 

128 """Parameters to debug""" 

129 titles = self.parallel_dimensions.keys() + list(self.info.keys()) 

130 titles = [str(t) for t in titles] 

131 return ",".join(titles) + "\n" 

132 

133 def values(self): 

134 """values debugged""" 

135 str_dims = [str(v) for v in self.parallel_dimensions.values()] 

136 str_score = [str(int(v)) for v in self.info.values()] 

137 return ",".join(str_dims + str_score) + "\n" 

138 

139 def write(self): 

140 """Parameters to debug""" 

141 if self.enable: 

142 os.makedirs(os.path.dirname(self.output_file), exist_ok=True) 

143 is_new = not os.path.exists(self.output_file) 

144 logger.info("debug written") 

145 with open(self.output_file, "a", encoding="utf-8") as outfile: 

146 if is_new: 

147 outfile.write(self.column_titles()) 

148 outfile.write(self.values()) 

149 

150 

151def pastel(color, l_delta=0.0, lbl=None, sat=None): 

152 "Pastel (lighter) color of the input" 

153 if color == "white": 

154 return (1.0, 1.0, 1.0) 

155 if color == "black": 

156 return (0.5, 0.5, 0.5) 

157 try: 

158 color = mc.cnames[color] 

159 except KeyError: 

160 pass 

161 color_hls = colorsys.rgb_to_hls(*mc.to_rgb(color)) 

162 lgt = 0.7 

163 if lbl is not None: 

164 lgt = lbl 

165 lgt = lgt + l_delta 

166 

167 if sat is None: 

168 sat = 0.6 

169 return colorsys.hls_to_rgb(color_hls[0], lgt, sat) 

170 

171 

172def near_white(color, ratio): 

173 "Very light color of the input for background" 

174 rgb = mc.to_rgb(color) 

175 if rgb is None: 

176 return "white" 

177 (red, green, blue) = rgb 

178 red += (1 - red) * ratio 

179 green += (1 - green) * ratio 

180 blue += (1 - blue) * ratio 

181 return (red, green, blue) 

182 

183 

184def dim_color(dim, default="black"): 

185 """Color of parallel dimensions for plot""" 

186 color = { 

187 Dim.DP: "orange", 

188 Dim.OP: "orange", 

189 Dim.TP: "red", 

190 Dim.EP: "blue", 

191 Dim.CP: "teal", 

192 Dim.PP: "green", 

193 Dim.VPP: "green", 

194 Dim.MBN: "green", 

195 } 

196 try: 

197 dim = Dim.get_dim(dim) 

198 if dim in color: 

199 return color[dim] 

200 return default 

201 except ValueError: 

202 return default 

203 

204 

205def gen_colors(categories): 

206 """Color of each time component""" 

207 compute_color = "purple" 

208 idle_color = "grey" 

209 col_d = { 

210 str(PerfParts.FW_COMPUTE): pastel(compute_color, -0.2), 

211 str(PerfParts.BW_COMPUTE): pastel(compute_color, -0.1), 

212 str(PerfParts.RECOMPUTE): pastel(compute_color), 

213 str(PerfParts.DP_COMM): pastel(dim_color(Dim.DP)), 

214 str(PerfParts.MP_COMM): pastel(dim_color(Dim.TP), -0.1), 

215 str(PerfParts.EP_COMM): pastel(dim_color(Dim.EP)), 

216 str(PerfParts.CP_COMM): pastel(dim_color(Dim.CP)), 

217 str(PerfParts.PP_COMM): pastel(dim_color(Dim.PP)), 

218 str(PerfParts.BUBBLE): pastel(dim_color(Dim.PP), -0.15), 

219 "IDLE": idle_color, 

220 "COMPUTATION": pastel(compute_color, -0.2), 

221 } 

222 return [col_d.get(cat) for cat in categories] 

223 

224 

225def set_twin_handles(ax1, data_frame, dbg_cols): 

226 """Set legend for estimation and real""" 

227 handle1, label1 = ax1.get_legend_handles_labels() 

228 ax2 = plt.twinx() 

229 data_frame[dbg_cols].plot.bar( 

230 stacked=True, 

231 sharex=True, 

232 ax=ax2, 

233 position=0, 

234 color=gen_colors(dbg_cols), 

235 width=0.4, 

236 rot=0, 

237 ) 

238 

239 handle2, label2 = ax2.get_legend_handles_labels() # type: ignore 

240 for handle in handle2: 

241 if handle not in handle1: 

242 handle1.append(handle) 

243 for lbl in label2: 

244 if lbl not in label1: 

245 label1.append(lbl) 

246 handles = handle1 

247 labels = label1 

248 plt.legend(handles, labels, loc="upper left", bbox_to_anchor=(1, 1)) 

249 leg = ax2.get_legend() 

250 pp_color = gen_colors(["PP_COMM"])[0] 

251 leg.legend_handles[-1].set_facecolor(pp_color) # type: ignore 

252 

253 

254class Plot: 

255 """plot ND top configs""" 

256 

257 title: str 

258 col_title: list[str] 

259 row_title: list[str] 

260 cell_text: list[list[str]] 

261 data: list[tuple] 

262 dbg_cols: list[str] 

263 top: int 

264 

265 def __init__(self, title, rows, debug_parts, top=None): 

266 self.title = title 

267 self.top = top if top is not None else 20 

268 self.row_title = rows + ["MEM"] 

269 self.dbg_cols = list(map(str, debug_parts)) 

270 self.col_title = [] 

271 self.cell_text = [] 

272 self.data = [] 

273 

274 def make_table(self): 

275 """Make table below plot with each parallelism degree""" 

276 self.cell_text = list(map(list, zip(*self.cell_text))) # transpose 

277 max_rows = list(map(max, map(partial(map, float), self.cell_text))) 

278 the_table = plt.table( 

279 cellText=self.cell_text, 

280 rowLabels=self.row_title, 

281 colLabels=self.col_title, 

282 cellLoc="center", 

283 loc="bottom", 

284 ) 

285 row_colors = list(map(dim_color, self.row_title)) 

286 for row in range(len(self.row_title)): 

287 cell = the_table[row + 1, -1] 

288 cell.set_edgecolor("none") 

289 cell.get_text().set_color(row_colors[row]) 

290 cell.set_text_props(fontproperties=FontProperties(weight="bold")) 

291 for col in range(len(self.cell_text[0])): 

292 cell = the_table[row + 1, col] 

293 value = float(str(cell.get_text().get_text())) 

294 try: 

295 ratio = 1 - (value / max_rows[row]) 

296 except ZeroDivisionError: 

297 ratio = 0 

298 logger.debug( 

299 "tmax = %s, ratio = %f, col=%s, newcolor=%s", 

300 str(max_rows[row]), 

301 ratio, 

302 str(mc.to_rgb(row_colors[row])), 

303 str(near_white(row_colors[row], ratio)), 

304 ) 

305 cell.set_facecolor(near_white(pastel(row_colors[row]), ratio)) 

306 cell.set_edgecolor("none") 

307 

308 for col in range(len(self.cell_text[0])): 

309 the_table[0, col].set_edgecolor("none") 

310 

311 the_table.scale(xscale=1, yscale=1.2) # +len(rows)/5) 

312 

313 def close(self, output_path, filename): 

314 """Plot closing statements""" 

315 plt.gca().set_xticklabels([]) 

316 plt.gca().set_yticklabels([]) 

317 plt.xlim([-0.5, len(self.data) - 0.5]) 

318 if self.title is not None: 

319 plt.title(self.title) 

320 plt.subplots_adjust(left=0.1, bottom=0.047 * (2 + len(self.row_title))) 

321 plotfile = os.path.join(output_path, filename + ".pdf") 

322 plt.savefig(plotfile, bbox_inches="tight") 

323 plt.clf() 

324 

325 def parse_data( 

326 self, 

327 configs_estimated, 

328 **kwargs, 

329 ): 

330 """Parse test data for plot""" 

331 real_data = kwargs.get("real_data", None) 

332 plot_idle = kwargs.get("plot_idle", False) 

333 min_e = configs_estimated[0][2] 

334 i = 0 

335 for cfg_e in configs_estimated: 

336 self.cell_text.append(cfg_e[0].values() + [cfg_e[1]]) 

337 self.col_title.append("") 

338 try: 

339 self.data.append( 

340 tuple([cfg_e[0], cfg_e[2], cfg_e[3]] + cfg_e[4]) 

341 ) 

342 if real_data is not None: 

343 waits = cfg_e[5] 

344 logger.info(waits) 

345 wait_list = [ 

346 waits["comp"], 

347 waits["dp_wait"], 

348 waits["mp_wait"], 

349 waits["ep_wait"], 

350 waits["BUBBLE"], 

351 ] 

352 if plot_idle: 

353 wait_list.append(waits["IDLE"]) 

354 real_data.append(tuple(wait_list)) 

355 except IndexError: 

356 score = cfg_e[2] 

357 if i >= self.top or (min_e is not None and score > min_e * 20): 

358 self.cell_text.pop() 

359 break 

360 self.data.append(tuple([cfg_e[0], score] + cfg_e[3])) 

361 i += 1 

362 

363 

364def plot_nd( 

365 configs_estimated, output_path, debug_parts, title=None, max_num=None 

366): 

367 """Plot estimation""" 

368 plot = Plot( 

369 title, configs_estimated[0][0].keys(), debug_parts, top=max_num 

370 ) 

371 plot.parse_data(configs_estimated) 

372 

373 data_frame = pd.DataFrame( 

374 plot.data, columns=(["config", "estim"] + plot.dbg_cols) 

375 ) 

376 axis = data_frame[plot.dbg_cols].plot.bar( 

377 stacked=True, color=gen_colors(plot.dbg_cols), width=0.4, rot=0 

378 ) 

379 axis.set_ylim(ymin=1) 

380 axis.legend(loc="upper left", bbox_to_anchor=(1, 1)) 

381 

382 plot.make_table() 

383 plot.close(output_path, "results") 

384 

385 

386def plot_vs_real( 

387 configs_estimated, csv_f, output_path, debug_parts, title=None 

388): 

389 """Plot estimation vs real global time""" 

390 plot = Plot(title, configs_estimated[0][0].keys(), debug_parts) 

391 plot.parse_data(configs_estimated) 

392 

393 data_frame = pd.DataFrame( 

394 plot.data, columns=(["config", "Real", "estim"] + plot.dbg_cols) 

395 ) 

396 ax1 = data_frame["Real"].plot.bar( 

397 position=1.1, width=0.4, secondary_y="real", color="grey", rot=0 

398 ) 

399 

400 set_twin_handles(ax1, data_frame, plot.dbg_cols) 

401 plot.make_table() 

402 plot.close(output_path, Path(os.path.basename(csv_f)).stem) 

403 

404 

405def plot_vs_real_comm_classified( 

406 configs_estimated, 

407 csv_f, 

408 output_path, 

409 debug_parts, 

410 **kwargs, 

411): 

412 """Plot estimation vs real detailed time""" 

413 plot_idle = kwargs.get("plot_idle", False) 

414 title = kwargs.get("title", None) 

415 real_data = [] 

416 

417 plot = Plot(title, configs_estimated[0][0].keys(), debug_parts) 

418 plot.parse_data( 

419 configs_estimated, 

420 real_data=real_data, 

421 plot_idle=plot_idle, 

422 ) 

423 

424 data_frame = pd.DataFrame( 

425 plot.data, columns=(["config", "real", "estim"] + plot.dbg_cols) 

426 ) 

427 real_cols = [ 

428 "COMPUTATION", 

429 "DP_COMM", 

430 "MP_COMM", 

431 "EP_COMM", 

432 "BUBBLE", 

433 ] 

434 if plot_idle: 

435 real_cols.append("IDLE") 

436 real_df = pd.DataFrame(real_data, columns=real_cols) 

437 

438 ax1 = real_df[real_cols].plot.bar( 

439 stacked=True, 

440 sharex=True, 

441 position=1, 

442 secondary_y="real", 

443 color=gen_colors(real_cols), 

444 width=0.4, 

445 rot=0, 

446 legend=False, 

447 ) 

448 

449 set_twin_handles(ax1, data_frame, plot.dbg_cols) 

450 plot.make_table() 

451 plot.close( 

452 output_path, 

453 Path(os.path.basename(csv_f)).stem, 

454 ) 

455 

456 

457def correlation_topk(configs_estimated, csv_f): 

458 """Computes correlation & top-k between real & estimation""" 

459 times = [] 

460 estims = [] 

461 for _, _, time, score, _ in configs_estimated: 

462 times.append(time) 

463 estims.append(score) 

464 correl = pearsonr(times, estims).statistic # type: ignore 

465 if isnan(correl): 

466 logger.critical( 

467 "An input array is constant: %s or %s", str(times), str(estims) 

468 ) 

469 topk = 0 

470 for i, score in enumerate(estims): 

471 if not score == min(estims[i:]): 

472 break 

473 topk += 1 

474 if topk == 0: 

475 for i, score in enumerate(estims): 

476 if score == min(estims[i:]): 

477 break 

478 topk -= 1 

479 

480 logger.info("Correlation for file %s is: %.3f", csv_f, correl * 100) 

481 return correl, topk 

482 

483 

484def get_real_data(csv_f): 

485 """Read execution time of different configurations on a given csv file""" 

486 configs = [] 

487 row_num = 0 

488 with open(csv_f, newline="", encoding="utf-8") as csv_file: 

489 rows = csv.DictReader(csv_file) 

490 for row in rows: 

491 row_num += 1 

492 logger.info(row) 

493 real_time = float(row.pop("time")) 

494 config = [] 

495 for dim_str, value in row.items(): 

496 try: 

497 dim = Dim.get_dim(dim_str) 

498 logger.debug("%s : %s", str(dim), str(dim.from_str(value))) 

499 config.append((dim, dim.from_str(value))) 

500 except ValueError: 

501 pass 

502 configs.append((Dim.Dimensions(config), real_time)) 

503 return configs, row_num 

504 

505 

506def get_diff_dims(csv_f): 

507 """Read execution time of different configurations on a given csv file""" 

508 dims = [] 

509 data_frame = pd.read_csv(csv_f) 

510 for dim_str, degrees in data_frame.items(): 

511 try: 

512 dim = Dim.get_dim(dim_str) 

513 diff_values = len(set(degrees)) 

514 if diff_values > 1: 

515 dims.append(dim) 

516 except ValueError: 

517 pass 

518 return dims 

519 

520 

521def get_comm_classified_data(csv_f, plot_idle=False): 

522 """Read time components of different configurations on a given csv file""" 

523 configs = [] 

524 with open(csv_f, newline="", encoding="utf-8") as csv_file: 

525 rows = csv.DictReader(csv_file) 

526 for row in rows: 

527 logger.info(row) 

528 time = float(row.pop("time")) 

529 config = [] 

530 comm_wait_time_classified = {} 

531 total_wait = 0 

532 

533 for component, value_str in row.items(): 

534 if "wait" in component: 

535 value_float = float(value_str) 

536 logger.info( 

537 "Comm_wait = %s, v = %f", component, value_float 

538 ) 

539 comm_wait_time_classified[component] = value_float 

540 total_wait += value_float 

541 elif "comp" in component: 

542 value_float = float(value_str) 

543 logger.info("Computation = %f", value_float) 

544 comm_wait_time_classified["comp"] = value_float 

545 total_wait += value_float 

546 else: 

547 logger.info("d = %s, v = %s", component, value_str) 

548 dim = Dim.get_dim(component) 

549 config.append((dim, dim.from_str(value_str))) 

550 comm_wait_time_classified["BUBBLE"] = comm_wait_time_classified.get(str(RealParts.PP_WAIT)) 

551 if plot_idle: 

552 comm_wait_time_classified["IDLE"] = time - total_wait 

553 logger.info( 

554 "idle = total time - total waits = %.3f - %.3f", 

555 time, 

556 total_wait, 

557 ) 

558 configs.append( 

559 (Dim.Dimensions(config), time, comm_wait_time_classified) 

560 ) 

561 return configs 

562 

563 

564def estimation_in_real_parts( 

565 estimations_in_real_components, estimations, score 

566): 

567 """Transform the estimation components into the RealParts components for comparison with real time""" 

568 estimations_in_real_components[RealParts.TOTAL].append(score) 

569 estimations_in_real_components[RealParts.COMP].append( 

570 estimations[PerfParts.FW_COMPUTE.value - 1] 

571 + estimations[PerfParts.BW_COMPUTE.value - 1] 

572 + estimations[PerfParts.RECOMPUTE.value - 1] 

573 ) 

574 estimations_in_real_components[RealParts.DP_WAIT].append( 

575 estimations[PerfParts.DP_COMM.value - 1] 

576 ) 

577 estimations_in_real_components[RealParts.MP_WAIT].append( 

578 estimations[PerfParts.MP_COMM.value - 1] 

579 ) 

580 estimations_in_real_components[RealParts.CP_WAIT].append( 

581 estimations[PerfParts.CP_COMM.value - 1] 

582 ) 

583 estimations_in_real_components[RealParts.EP_WAIT].append( 

584 estimations[PerfParts.EP_COMM.value - 1] 

585 ) 

586 estimations_in_real_components[RealParts.PP_WAIT].append( 

587 estimations[PerfParts.BUBBLE.value - 1] 

588 + estimations[PerfParts.PP_COMM.value - 1] 

589 ) 

590 return estimations_in_real_components 

591 

592 

593def real_in_parts(parts, real, time): 

594 """Transform the real time components into the RealParts components for comparison with estimation""" 

595 parts[RealParts.TOTAL].append(time) 

596 for part in RealParts: 

597 if part not in {RealParts.TOTAL, RealParts.IDLE}: 

598 if str(part) in real.keys(): 

599 parts[part].append(real[str(part)]) 

600 else: 

601 logger.warning( 

602 "part = %s not in real keys = %s", part, real.keys() 

603 ) 

604 parts[part].append(0) 

605 

606 op = "op_wait" 

607 if op in real.keys(): 

608 parts[RealParts.DP_WAIT][-1] += real["op_wait"] 

609 

610 sp = "sp_wait" 

611 if sp in real.keys(): 

612 parts[RealParts.MP_WAIT][-1] += real["sp_wait"] 

613 

614 return parts 

615 

616 

617def correlation_with_classified_comms(configs_estimated): 

618 """Computes correlation and distance between components time & estimation.""" 

619 score_classified = {} 

620 time_classified = {} 

621 distances = {} 

622 

623 for wait in RealParts: 

624 if wait not in {RealParts.IDLE}: 

625 score_classified[wait] = [] 

626 time_classified[wait] = [] 

627 distances[wait] = [] 

628 

629 topk = 0 

630 still_top_k = True 

631 

632 for i, (_, _, time, score, values, real_values) in enumerate( 

633 configs_estimated 

634 ): 

635 if ( 

636 still_top_k 

637 and score == (min(configs_estimated[i:], key=lambda t: t[3]))[3] 

638 ): 

639 topk += 1 

640 else: 

641 still_top_k = False 

642 

643 score_classified = estimation_in_real_parts( 

644 score_classified, values, score 

645 ) 

646 

647 time_classified = real_in_parts(time_classified, real_values, time) 

648 

649 square_distances_sum = 0 

650 for wait in RealParts: 

651 if wait not in {RealParts.TOTAL, RealParts.IDLE}: 

652 distance = ( 

653 time_classified[wait][-1] 

654 / time_classified[RealParts.TOTAL][-1] 

655 - score_classified[wait][-1] 

656 / score_classified[RealParts.TOTAL][-1] 

657 ) 

658 square_distances_sum += distance * distance 

659 distances[wait].append(abs(distance)) 

660 distances[RealParts.TOTAL] = sqrt(square_distances_sum) 

661 

662 correls = {} 

663 for wait in RealParts: 

664 pearson_wait(correls, time_classified, score_classified, wait) 

665 return correls, distances, topk, len(configs_estimated) 

666 

667 

668def color_diff(diff): 

669 """Color difference""" 

670 if diff > 0: 

671 return f"\033[92m improved by {diff:.3f}%\033[00m" 

672 return f"\033[91m worsened by {-diff:.3f}%\033[00m" 

673 

674 

675def color_correl(correlation): 

676 """Color correlation""" 

677 res = f"{correlation*100:.3f}%" 

678 if correlation > 0.9: 

679 res = f" \033[92m{res}\033[00m " 

680 elif correlation < 0: 

681 res = f"\033[91m{res}\033[00m " 

682 elif correlation < 0.5: 

683 res = f" \033[91m{res}\033[00m " 

684 else: 

685 res = f" \033[00m{res}\033[00m " 

686 return res 

687 

688 

689def print_diff(case, prev, new, **kwargs): 

690 """Print difference of correlation""" 

691 topk = kwargs.get("topk", None) 

692 total = kwargs.get("total", None) 

693 tabsize = kwargs.get("tabsize", 40) 

694 diff = (new - prev) * 100 

695 msg = "" 

696 if -0.1 < diff < 0.1: 

697 msg = f"{case} \tcorrelation :{color_correl(new)} \033[00m\033[00m" 

698 else: 

699 msg = ( 

700 f"{case} \tcorrelation ({color_correl(new)}) is{color_diff(diff)}" 

701 ) 

702 if topk is not None and total is not None: 

703 msg += f" topk = {topk}/{total}" 

704 logger.output(msg.expandtabs(tabsize)) 

705 

706 

707def get_distance_i(part, data_i): 

708 """get the average distance of a given part""" 

709 _, distance, _, _ = data_i 

710 if part is RealParts.TOTAL: 

711 return distance[part] 

712 return sum(distance[part]) / len(distance[part]) 

713 

714 

715def get_correl_i(part, data_i): 

716 """get the correlation of a given part""" 

717 f_correl, _, _, _ = data_i 

718 return f_correl[part] 

719 

720 

721def print_part_x_file(data, fun): 

722 """Prints a metric computed by fun for each couple (part, file)""" 

723 msg = "" 

724 for part in RealParts: 

725 if part is not RealParts.IDLE: 

726 msg += "\n" + str(part) + "\t" 

727 col_sum = 0 

728 col_num = 0 

729 for data_i in data: 

730 try: 

731 info = fun(part, data_i) 

732 msg += f"\t{(info*100):.1f}%" 

733 col_sum += info 

734 col_num += 1 

735 except KeyError: 

736 msg += "\t -" 

737 if col_num > 0: 

738 msg += f"\t\t{(col_sum/col_num)*100:.1f}%" 

739 return msg 

740 

741 

742def print_correlations_classified(data): 

743 """Printer for estimation vs detailed profiling""" 

744 msg = "\n\t" 

745 for i, _ in enumerate(data): 

746 msg += "\tFile " + str(i + 1) 

747 msg += "\t\tavg" 

748 

749 msg += "\nCorrelation (higher is better)" 

750 msg += print_part_x_file(data, get_correl_i) 

751 

752 msg += "\ntop_k\t" 

753 for _, _, top_k, total in data: 

754 msg += "\t" + str(top_k) + "/" + str(total) 

755 

756 msg += "\n\nEuclidean Distance (lower is better)" 

757 msg += print_part_x_file(data, get_distance_i) 

758 

759 logger.output(msg) 

760 

761 

762def is_constant(array): 

763 """Whether the given array only has the same elements""" 

764 if len(array) == 0: 

765 return True 

766 value = array[0] 

767 return all(vi == value for vi in array) 

768 

769 

770def pearson_wait(correls, real, estim, wait): 

771 """Compute Pearson correlation if inputs are not empty""" 

772 if wait not in {RealParts.IDLE}: 

773 logger.debug( 

774 "correlation for %s between real = %s && estim = %s", 

775 str(wait), 

776 str(real[wait]), 

777 str(estim[wait]), 

778 ) 

779 if not is_constant(real[wait]) and not is_constant(estim[wait]): 

780 pearson = pearsonr( 

781 real[wait], estim[wait] 

782 ).statistic # type: ignore 

783 logger.info( 

784 "correlation[%s] of real %s vs estim %s = %f", 

785 wait, 

786 real[wait], 

787 estim[wait], 

788 pearson, 

789 ) 

790 correls[wait] = pearson 

791 else: 

792 logger.warning( 

793 "either estim[%s] = %s is constant", wait, str(estim[wait]) 

794 ) 

795 logger.warning( 

796 "or real[%s] = %s is constant", wait, str(real[wait]) 

797 )