-
Notifications
You must be signed in to change notification settings - Fork 0
/
solution_metrics.py
357 lines (260 loc) · 11.6 KB
/
solution_metrics.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
"""
Script to measure the quality of the solutions provided.
Uses the input file to extract the driver pins and the pins to route.
It will then perform the following:
- Check all the pins are routed.
- Check all the chains are valid (starts with 1 driver input and ends with 1 driver output)
- Count the number of chains made.
- Measure the length of every chain.
Call the script using
`python solution_metrics.py input_file_name output_file_name`
"""
import collections
import statistics
import sys
import traceback
Point = collections.namedtuple("Point", ["x", "y"])
Pin = collections.namedtuple("Pin", ["name", "loc", "visited"])
# ---------------------------------------------------------------------------------------------------------------------
def extract_pins(input_file):
"""Extracts the list of pins (drivers and pins to route) from the input file.
Args:
input_file {str} -- The name of the input file.
Returns:
(list(Pin), list(Pin)) -- The list of driver pins and the list of pins to route.
"""
header_done = False
# previous_was_row = False
previous_was_die_area = False
driver_pins = []
pins_to_route = []
driver_name = ""
ongoing_driver = 0 # Used to skip lines
with open(input_file, "r") as f:
for line in f.readlines():
# Ignore the header and the rows:
if not header_done:
if line == "\n" and previous_was_die_area:
header_done = True
if line == "\n":
continue
if line.split()[0] == "DIEAREA":
previous_was_die_area = True
if line == "\n":
continue
# Extract driver pins:
if "DRIVERPIN_" in line:
driver_name = line.split()[1]
ongoing_driver = 2
continue
if ongoing_driver > 1:
ongoing_driver -= 1
continue
if ongoing_driver == 1:
p = Point(*[int(s) for s in line.split() if s.isdigit()])
driver_pins.append(Pin(driver_name, p, False))
ongoing_driver -= 1
# Extract pins:
if "im_psyched" in line:
sp = line.split()
p = Point(*[int(s) for s in sp if s.isdigit()])
pins_to_route.append(Pin(sp[0], p, False))
return driver_pins, pins_to_route
# ---------------------------------------------------------------------------------------------------------------------
def extract_links(output_file):
"""Extracts the links from the generated output file. They might not be in order
so this is just extracting the links.
Args:
output_file {str}: The name of the output file to parse.
Returns:
dict(str: str) -- The links between the pins.
"""
links = {}
conn_in = ""
with open(output_file, "r") as f:
for line in f.readlines():
# Skip the semi-colon lines if they are separated:
if line.strip() == ";":
continue
# Skip the net names, not used:
if line[0] == "-":
continue
# Get conn_in
if conn_in == "":
conn_in = line.split()[1]
# Get conn_out
else:
conn_out = line.split()[1]
links[conn_in] = conn_out
conn_in = ""
return links
# ---------------------------------------------------------------------------------------------------------------------
def extract_chains(links, pins_index):
"""Extract the chains of pins from the output file.
Args:
links {dict(str: str)} -- The links between the pins extracted from the ouput.
pins_index {dict(str: Pin)} -- The index of pins to route and driver pins based on their name.
Raises:
ValueError -- If the number of driver pins used is not right or if a drive pin is used multiple times.
Returns:
list(list(Pin)) -- The list of chains of pins.
"""
chains = []
# Search for the driver inputs:
inputs = [p for p in links.keys() if pin_is_input_driver(p)]
outputs = [p for p in links.keys() if pin_is_output_driver(p)]
if not len(inputs) != len(outputs):
raise ValueError("The same number of input and ouput pins should be used from the driver.")
if not (2 <= len(inputs) <= 16):
raise ValueError(f"The number of chains should be between 2 and 16, currently {len(input)}.")
print("Valid number of driver pins used: check")
if len(inputs) != len(set(inputs)) or len(outputs) != len(set(outputs)):
raise ValueError("Driver pins should be used only once each maximum.")
print("Driver pins are used only once: check")
# Follow the trail until it reaches a driver output:
for i in inputs:
chain = []
chain.append(pins_index[i])
next_pin = pins_index[i]
while not pin_is_output_driver(next_pin):
next_pin = pins_index[links[next_pin.name]]
if next_pin.visited:
raise Exception("Loop detected in a chain. Pins should be routed only once.")
chain.append(next_pin)
chains.append(chain)
return chains
# ---------------------------------------------------------------------------------------------------------------------
def check_valid_chain(chain):
"""Checks that the provided chain is valid (i.e. starts with an input pin, ends with
an output pin).
Args:
chain {list(str)} -- The chain to test.
Returns:
bool -- Returns True if the chain is valid, False otherwise.
"""
return pin_is_input_driver(chain[0]) and pin_is_output_driver(chain[-1])
# ---------------------------------------------------------------------------------------------------------------------
def pin_is_input_driver(pin):
"""Check if the provided pin is an driver input pin.
Args:
pin {Pin | str}: The pin to check.
Returns:
bool -- Returns True if the pin is a driver input pin, False otherwise.
"""
if isinstance(pin, Pin):
pin = pin.name
if "DRIVERPIN_" not in pin:
return False
return int(pin.split("_")[1]) <= 15
# ---------------------------------------------------------------------------------------------------------------------
def pin_is_output_driver(pin):
"""Check if the provided pin is an driver output pin.
Args:
pin {Pin | str}: The pin to check.
Returns:
bool -- Returns True if the pin is a driver output pin, False otherwise.
"""
if isinstance(pin, Pin):
pin = pin.name
if "DRIVERPIN_" not in pin:
return False
return int(pin.split("_")[1]) > 15
# ---------------------------------------------------------------------------------------------------------------------
def check_all_pins_routed(chains, pins_to_route):
"""Checks that all the pins to route are part of a chain.
And that each pin is routed only once.
Args:
chains {list(list(Pin))} -- The list of chains of pins.
pins_to_route {list(Pin)} -- The list of pins to route.
Returns:
bool -- Returns True if all pins are routed, False otherwise.
"""
to_route = set([p.name for p in pins_to_route])
found = set()
for chain in chains:
for pin in chain:
if pin_is_input_driver(pin) or pin_is_output_driver(pin):
continue
if pin.name in found:
return False
found.add(pin.name)
return to_route == found
# ---------------------------------------------------------------------------------------------------------------------
def manhattan_distance(a, b):
"""Computes the Manhattan distance between two points."""
return abs(a.x - b.x) + abs(a.y - b.y)
# ---------------------------------------------------------------------------------------------------------------------
def measure_chain_length(chain):
"""Measures the length of the provided chain based on the location of the pins.
Args:
chain {list(Pin)} -- A chain of pins.
Return:
int -- The length of the provided chain based on Manhattan's distance.
"""
distance = 0
for i in range(len(chain) - 1):
distance += manhattan_distance(chain[i].loc, chain[i + 1].loc)
return distance
# ---------------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------
def solution_metrics(input_file, output_file):
"""Extract metrics from the provided output file based on the input file.
Args:
input_file {str} -- The name of the input file used in the problem.
output_file {str} -- The name of the provided output file.
Return:
(float, float, float) -- The average chain length, the std deviation, and the difference
between the longest and the shortest chain.
"""
# Parse input and output files:
driver_pins, pins_to_route = extract_pins(input_file)
try:
links = extract_links(output_file)
print("Output file formatted properly: check")
except Exception as e:
print(
"Encountered an exception while trying to parse the output:\n",
traceback.print_exception(e),
"\nThis solution does not meet the required format.",
)
exit(1)
# Index the data:
pin_locations = {}
for pin in driver_pins:
pin_locations[pin.name] = pin.loc
for pin in pins_to_route:
pin_locations[pin.name] = pin.loc
pins_index = {}
for pin in driver_pins:
pins_index[pin.name] = pin
for pin in pins_to_route:
pins_index[pin.name] = pin
# Extract the chains and checks they're okay:
try:
chains = extract_chains(links, pins_index)
print("Chains could be extracted: check")
for chain in chains:
assert check_valid_chain(chain), "Chains should start and end at the driver."
print("Chains start and end at the driver: check")
assert check_all_pins_routed(chains, pins_to_route), "All pins should be routed exactly once."
print("All pins are routed exactly once: check")
except Exception as e:
print(
"Encountered an exception while trying to extract the chains:\n",
traceback.print_exception(e),
"\nThis solution does not meet the required constraints on the chains.",
)
exit(1)
lengths = []
print("-" * 40)
print(f"Number of chains formed: {len(chains)}")
for i, chain in enumerate(chains):
lengths.append(measure_chain_length(chain))
print(f" - Chain {i} - Length = {lengths[-1]}")
print(f"Average length = {sum(lengths)/len(lengths)}")
print(f"Standard deviation = {statistics.stdev(lengths)}")
print(f"Difference max-min = {max(lengths) - min(lengths)}")
return sum(lengths) / len(lengths), statistics.stdev(lengths), max(lengths) - min(lengths)
if __name__ == "__main__":
solution_metrics(sys.argv[1], sys.argv[2])