Initial commit
This commit is contained in:
276
challenge_evaluation.py
Normal file
276
challenge_evaluation.py
Normal file
@ -0,0 +1,276 @@
|
||||
import argparse
|
||||
import zipfile
|
||||
import json
|
||||
import os
|
||||
|
||||
# Costs
|
||||
CONSTANT_COST = 1
|
||||
REGISTER_COST = 1
|
||||
OP_COST = {
|
||||
"SUB": 4,
|
||||
"ADD": 4,
|
||||
"INC": 4,
|
||||
"DEC": 4,
|
||||
"MUL": 10,
|
||||
"DIV": 10,
|
||||
"MOD": 10,
|
||||
"OR": 8,
|
||||
"AND": 8,
|
||||
"XOR": 8,
|
||||
"INV": 8,
|
||||
"SL": 5,
|
||||
"SR": 5,
|
||||
"SLU": 5,
|
||||
"SRU": 5,
|
||||
"ROTL": 7,
|
||||
"ROTR": 7,
|
||||
}
|
||||
ALG_LINE_COST = 0.5
|
||||
|
||||
# ansi escape codes
|
||||
ul = "\033[4m" # underline
|
||||
end = "\033[0m" # reset
|
||||
ylw = "\033[33m" # yellow
|
||||
|
||||
|
||||
def is_empty_row(row, pedantic=False):
|
||||
# Check if signal table is non-empty
|
||||
if "signal" in row and len(row["signal"]) != 0:
|
||||
return False
|
||||
|
||||
# Only check if keys are set if pedantic is true
|
||||
if not pedantic:
|
||||
return True
|
||||
# Check if "unconditional-jump", "conditional-jump" or "label" is set
|
||||
keys = ("unconditional-jump", "conditional-jump", "label")
|
||||
return not any(key in row for key in keys)
|
||||
|
||||
|
||||
def evaluate(filepath, verbose=False, pedantic=False):
|
||||
filename = filepath.split(os.path.sep)[-1]
|
||||
if not filepath.endswith(".zip"):
|
||||
print(
|
||||
f"{filename} :: Supplied file does not have .zip file extension. Skipping .."
|
||||
)
|
||||
return -1
|
||||
|
||||
with zipfile.ZipFile(filepath, "r") as savefile:
|
||||
with savefile.open("machine.json", "r") as machinefile, savefile.open(
|
||||
"signal.json", "r"
|
||||
) as signalfile:
|
||||
machine = json.load(machinefile)
|
||||
signal = json.load(signalfile)
|
||||
|
||||
# Load lines of code (rows)
|
||||
total_rows = signal["signaltable"]["row"]
|
||||
# Remove rows without effect (used for formatting for example)
|
||||
rows = [row for row in total_rows if not is_empty_row(row, pedantic=pedantic)]
|
||||
if verbose:
|
||||
print(f"{filename} :: Total number of rows: {len(total_rows)}")
|
||||
print(f"{filename} :: Number of rows after excluding empty: {len(rows)}")
|
||||
|
||||
# Check if IR or PC register was used
|
||||
|
||||
pc_used = any(
|
||||
(
|
||||
signal["name"] == "PC.W" and signal["value"] == "1"
|
||||
for row in rows
|
||||
for signal in row["signal"]
|
||||
)
|
||||
)
|
||||
ir_used = any(
|
||||
(
|
||||
signal["name"] == "IR.W" and signal["value"] == "1"
|
||||
for row in rows
|
||||
for signal in row["signal"]
|
||||
)
|
||||
)
|
||||
|
||||
if verbose:
|
||||
if pc_used:
|
||||
print(f"{filename} :: PC Register was used in signal table row.")
|
||||
if ir_used:
|
||||
print(f"{filename} :: IR Register was used in signal table row.")
|
||||
|
||||
# Load used multiplexer constants
|
||||
try:
|
||||
mux_input_a = next(
|
||||
filter(lambda mux: mux["muxType"] == "A", machine["machine"]["muxInputs"])
|
||||
)["input"]
|
||||
mux_consts_a = [
|
||||
int(mux_input["value"])
|
||||
for mux_input in mux_input_a
|
||||
if mux_input["type"] == "constant"
|
||||
]
|
||||
except StopIteration:
|
||||
print(
|
||||
f"{filename} :: Couldn't find input for multiplexer A. Is the file corrupted? Skipping file .."
|
||||
)
|
||||
return -1
|
||||
try:
|
||||
mux_input_b = next(
|
||||
filter(lambda mux: mux["muxType"] == "B", machine["machine"]["muxInputs"])
|
||||
)["input"]
|
||||
mux_consts_b = [
|
||||
int(mux_input["value"])
|
||||
for mux_input in mux_input_b
|
||||
if mux_input["type"] == "constant"
|
||||
]
|
||||
except StopIteration:
|
||||
print(
|
||||
f"{filename} :: Couldn't find input for multiplexer B. Is the file corrupted? Skipping file .."
|
||||
)
|
||||
return -1
|
||||
|
||||
# Base machine has constants 0 and 1 at multiplexer A. All other constants are extensions.
|
||||
base_muxt_a = (0, 1)
|
||||
for base_input in base_muxt_a:
|
||||
try:
|
||||
mux_consts_a.remove(base_input)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
constants = set(mux_consts_a + mux_consts_b)
|
||||
|
||||
if verbose:
|
||||
print(
|
||||
f"{filename} :: Found {len(mux_consts_a)} constants for multiplexer A: {mux_consts_a}"
|
||||
)
|
||||
print(
|
||||
f"{filename} :: Found {len(mux_consts_b)} constants for multiplexer B: {mux_consts_b}"
|
||||
)
|
||||
print(
|
||||
f"{filename} :: Found {len(constants)} total unique constants: [{', '.join([str(c) for c in constants])}]"
|
||||
)
|
||||
|
||||
# Load used registers
|
||||
registers = machine["machine"]["registers"]["register"]
|
||||
registers = [register["name"] for register in registers]
|
||||
|
||||
if pc_used:
|
||||
registers.append("PC_ALT")
|
||||
if ir_used:
|
||||
registers.append("IR_ALT")
|
||||
|
||||
if verbose:
|
||||
print(f"{filename} :: Found {len(registers)} additional registers: {registers}")
|
||||
|
||||
# Load used operations
|
||||
operations = machine["machine"]["alu"]["operation"]
|
||||
base_operations = ("A_ADD_B", "B_SUB_A", "TRANS_A", "TRANS_B")
|
||||
for base_op in base_operations:
|
||||
try:
|
||||
operations.remove(base_op)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Extract operation (remove operands)
|
||||
def get_op(op_str):
|
||||
return next(filter(lambda substr: substr not in ("A", "B"), op_str.split("_")))
|
||||
|
||||
operations = list(map(get_op, operations))
|
||||
|
||||
if verbose:
|
||||
print(
|
||||
f"{filename} :: Found {len(operations)} additional operations: {operations}"
|
||||
)
|
||||
|
||||
# Sum points
|
||||
alg_line_costs = len(rows) * ALG_LINE_COST # every line of code
|
||||
constant_costs = len(constants) * CONSTANT_COST # constants at both multiplexers
|
||||
register_costs = len(registers) * REGISTER_COST # registers
|
||||
operation_costs = 0
|
||||
for operation in operations: # operations
|
||||
operation_costs += OP_COST[operation]
|
||||
|
||||
total = alg_line_costs + constant_costs + register_costs + operation_costs
|
||||
|
||||
# Summarize
|
||||
costs = (alg_line_costs, constant_costs, register_costs, operation_costs, total)
|
||||
precision = max(
|
||||
[len(str(float(cost)).split(".")[1].lstrip("0")) for cost in costs]
|
||||
) # unreadable but works ¯\_(ツ)_/¯
|
||||
|
||||
if verbose:
|
||||
print("")
|
||||
|
||||
print(f"{ul}Summary for {filename}:{end}\n")
|
||||
print(f" {alg_line_costs:5.{min(precision, 2)}f} LINES (excluding empty lines)")
|
||||
print(f"+ {constant_costs:5.{min(precision, 2)}f} CONSTANTS")
|
||||
print(f"+ {register_costs:5.{min(precision, 2)}f} REGISTERS")
|
||||
print(f"+ {operation_costs:5.{min(precision, 2)}f} OPERATIONS")
|
||||
print(f"-------------")
|
||||
print(f"= {ylw}{total:5.{min(precision, 2)}f} TOTAL{end}\n\n")
|
||||
|
||||
return total
|
||||
|
||||
|
||||
# Evaluation
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"source",
|
||||
type=str,
|
||||
nargs="+",
|
||||
help="Either ZIP file(s) generated by simulator or the submission root folder",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
dest="verbose",
|
||||
action="store_true",
|
||||
help="Prints additional information.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--pedantic",
|
||||
dest="pedantic",
|
||||
action="store_true",
|
||||
help="Extra pedantic (for example when checking for empty lines)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--top",
|
||||
dest="top",
|
||||
type=int,
|
||||
help="Print top n candidates (defaults to 7)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
verbose = args.verbose
|
||||
pedantic = args.pedantic
|
||||
top_n = args.top if args.top != None else 7
|
||||
|
||||
# output score for each file
|
||||
scores = []
|
||||
|
||||
# check if source argument is folder
|
||||
savefiles = []
|
||||
for source in args.source:
|
||||
if os.path.isdir(source):
|
||||
# add all zip from subdirectories
|
||||
for d in [
|
||||
e for e in os.listdir(source) if os.path.isdir(os.path.join(source, e))
|
||||
]:
|
||||
savefiles.append(os.path.join(source, d, f"{d}.zip"))
|
||||
elif source.endswith(".zip"):
|
||||
savefiles.append(source)
|
||||
else:
|
||||
print(f"Source '{source}' is not a ZIP file.")
|
||||
|
||||
for savefile in savefiles:
|
||||
score = evaluate(savefile, verbose=verbose, pedantic=pedantic)
|
||||
if score == -1:
|
||||
continue
|
||||
|
||||
scores.append([savefile, score])
|
||||
|
||||
# if there is more than one file, output top 3
|
||||
scores.sort(key=lambda x: x[1])
|
||||
|
||||
n = len(savefiles)
|
||||
if n > 1:
|
||||
print(f"{ul}Leaderboard:{end}")
|
||||
for i in range(min(n, top_n)):
|
||||
file, score = scores[i]
|
||||
print(f"#{i + 1} - {ylw}{score:5.2f} TOTAL{end} - {file}")
|
||||
Reference in New Issue
Block a user