#!/usr/bin/env python3 import argparse import re from collections import OrderedDict import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec import numpy as np from sklearn.metrics import mean_absolute_error, mean_squared_error class TimingPath: def __init__(self, startpoint, endpoint): self.startpoint = startpoint self.endpoint = endpoint def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("first_report"); parser.add_argument("second_report"); parser.add_argument("--type", choices=["path", "endpoint"], default="endpoint") return parser.parse_args() def main(): args = parse_args() print("Parsing {}".format(args.first_report)) first_paths = parse_timing_report(args, args.first_report) print("Parsing {}".format(args.second_report)) second_paths = parse_timing_report(args, args.second_report) plot_correlation(first_paths, second_paths, args.first_report, args.second_report) def parse_timing_report(args, filename): regex = re.compile(r".*?Endpoint : (?P\S+).*?", re.DOTALL) start_regex = re.compile(r"Startpoint: (?P\S+)") end_regex = re.compile(r"Endpoint\s*: (?P\S+)") slack_regex = re.compile(r"slack \((VIOLATED|MET)\)\s+?(?P\S+)") position_regex = re.compile(r"at \((?P\d+),(?P\d+)\)\)") lines = None with open(filename) as f: lines = f.readlines() lines = ''.join(lines) paths_lines = lines.split("#Path") paths = OrderedDict() for path_lines in paths_lines: distance = None startpoint = None endpoint = None slack = None prev_x = None prev_y = None match = start_regex.search(path_lines) if match: startpoint = match.groupdict()['start'] match = end_regex.search(path_lines) if match: endpoint = match.groupdict()['end'] match = slack_regex.search(path_lines) if match: slack = float(match.groupdict()['slack']) for match in position_regex.finditer(path_lines): x = int(match.groupdict()['x']) y = int(match.groupdict()['y']) if prev_x == None and prev_y == None: distance = 0 else: dx = abs(x - prev_x) dy = abs(y - prev_y) distance += dx + dy prev_x = x prev_y = y if endpoint == None: continue if args.type == "endpoint": start_end = (None, endpoint) else: start_end = (startpoint, endpoint) if start_end not in paths: paths[start_end] = slack, distance else: paths[start_end] = min(paths[start_end][0], slack), distance #Keep least slack return paths def correlate_paths(first_paths, second_paths): first_keys = set(first_paths.keys()) second_keys = set(second_paths.keys()) common_keys = first_keys & second_keys first_unique_keys = first_keys.difference(common_keys) second_unique_keys = second_keys.difference(common_keys) correlated_paths = [] first_only = [] second_only = [] for path in common_keys: correlated_paths.append( (path, first_paths[path], second_paths[path]) ) for path in first_unique_keys: first_only.append( (path, first_paths[path]) ) for path in second_unique_keys: second_only.append( (path, second_paths[path]) ) return correlated_paths, first_only, second_only def plot_correlation(first_paths, second_paths, first_name, second_name): correlated_paths, first_only, second_only = correlate_paths(first_paths, second_paths) print("Correlated {} paths".format(len(correlated_paths))) print("First only {} paths".format(len(first_only))) print("Second only {} paths".format(len(second_only))) first_slacks = [x[0] for x in first_paths.values()] second_slacks = [x[0] for x in second_paths.values()] first_only_slacks = [x[1][0] for x in first_only] second_only_slacks = [x[1][0] for x in second_only] min_value = min(min(first_slacks), min(second_slacks)) max_value = max(max(first_slacks), max(second_slacks)) x = [] y = [] dist = [] for (start, end), (first_slack, first_distance), (second_slack, second_distance) in correlated_paths: x.append(first_slack) y.append(second_slack) dist.append(first_distance) #Use first distance for now... if 0: #Correlation plot plt.subplot (5, 1, 1) plt.scatter(x, y) equal = np.linspace(*plt.xlim()) plt.plot(equal, equal) plt.xlabel("{} slack".format(first_name)) plt.ylabel("{} slack".format(second_name)) plt.xlim(min_value, max_value) plt.ylim(min_value, max_value) #First histogram nbins=30 plt.subplot (5, 1, 2) plt.hist(first_slacks, range=(min_value,max_value), log=True, bins=nbins) plt.xlim(min_value, max_value) plt.ylim(bottom=0.5) plt.xlabel("{} Slack".format(first_name)) plt.ylabel("Path Count") #Second histogram plt.subplot (5, 1, 3) plt.hist(second_slacks, range=(min_value,max_value), log=True, bins=nbins) plt.xlim(min_value, max_value) plt.ylim(bottom=0.5) plt.xlabel("{} Slack".format(second_name)) plt.ylabel("Path Count") #First residuals histogram plt.subplot (5, 1, 4) plt.hist(first_only_slacks, range=(min_value,max_value), log=True, bins=nbins) plt.xlim(min_value, max_value) plt.ylim(bottom=0.5) plt.xlabel("{} Residuals Slack".format(first_name)) plt.ylabel("Path Count") #Second residuals histogram plt.subplot (5, 1, 5) plt.hist(second_only_slacks, range=(min_value,max_value), log=True, bins=nbins) plt.xlim(min_value, max_value) plt.ylim(bottom=0.5) plt.xlabel("{} Residuals Slack".format(second_name)) plt.ylabel("Path Count") else: fig = plt.figure() gs = GridSpec(7,4) nbins=30 #Axies ax_scatter = fig.add_subplot(gs[1:4,0:3]) ax_cb = fig.add_axes([0.124, 0.375, 0.573, 0.03]) ax_hist_x = fig.add_subplot(gs[0,0:3]) ax_hist_y = fig.add_subplot(gs[1:4,3]) ax_error = fig.add_subplot(gs[0,3]) ax_second_hist = fig.add_subplot(gs[6,0:2]) ax_first_hist = fig.add_subplot(gs[5,0:2], sharex=ax_second_hist) ax_second_only_hist = fig.add_subplot(gs[6,2:4], sharey=ax_second_hist) ax_first_only_hist = fig.add_subplot(gs[5,2:4], sharex=ax_second_only_hist, sharey=ax_first_hist) #errors mae = mean_absolute_error(x, y) mse = mean_squared_error(x, y) ax_error.text(0.1, 0.1, s="MAE: {:.3f}\nMSE: {:.3f}\nWNS: {:.3f}".format(mae, mse, min(first_slacks))) ax_error.set_axis_off() #Scatter sc = ax_scatter.scatter(x,y, c=dist, vmin=0, facecolors='none', label="Paths/Endpoints") ax_scatter.set_xlim(min_value, max_value) ax_scatter.set_ylim(min_value, max_value) #Linear equal = np.linspace(*ax_scatter.get_xlim()) ax_scatter.plot(equal, equal, label="Ideal") ax_scatter.legend() fig.colorbar(sc, cax=ax_cb, orientation='horizontal', label="Path Distance") #Marginals ax_hist_x.hist(x, log=True, bins=nbins) ax_hist_x.set_ylim(bottom=0.5) ax_hist_x.set_xlim(min_value, max_value) ax_hist_y.hist(y, log=True, bins=nbins, orientation="horizontal") ax_hist_y.set_xlim(left=0.5) ax_hist_y.set_ylim(min_value, max_value) # Turn off tick labels plt.setp(ax_hist_x.get_xticklabels(), visible=False) plt.setp(ax_hist_y.get_yticklabels(), visible=False) #Full histograms ax_first_hist.set_title("Full Histograms") ax_first_hist.hist(first_slacks, log=True, bins=nbins) ax_first_hist.set_xlim(min_value, max_value) ax_first_hist.set_ylim(bottom=0.5) ax_first_hist.set_ylabel("{}\nCount".format(first_name)) plt.setp(ax_first_hist.get_xticklabels(), visible=False) ax_second_hist.hist(second_slacks, log=True, bins=nbins) ax_second_hist.set_xlim(min_value, max_value) ax_second_hist.set_ylim(bottom=0.5) ax_second_hist.set_ylabel("{}\nCount".format(second_name)) ax_second_hist.set_xlabel("Slack") #Residual histograms ax_first_only_hist.set_title("Residual Histograms") ax_first_only_hist.hist(first_only_slacks, log=True, bins=nbins) ax_first_only_hist.set_xlim(min_value, max_value) ax_first_only_hist.set_ylim(bottom=0.5) plt.setp(ax_first_only_hist.get_xticklabels(), visible=False) ax_second_only_hist.hist(second_only_slacks, log=True, bins=nbins) ax_second_only_hist.set_xlim(min_value, max_value) ax_second_only_hist.set_ylim(bottom=0.5) ax_second_only_hist.set_xlabel("Slack") # Set labels on joint ax_scatter.set_xlabel('{} slack'.format(first_name)) ax_scatter.set_ylabel('{} slack'.format(second_name)) # Set labels on marginals ax_hist_y.set_xlabel('Path/Endpoint Count') ax_hist_x.set_ylabel('Path/Endpoint Count') plt.tight_layout() plt.show() if __name__ == "__main__": main()