|
| 1 | +""" |
| 2 | +Evaluation for Challenge Suite Problem 4. |
| 3 | +
|
| 4 | +The evaluator dynamically imports a solution module, consumes only the NumPy |
| 5 | +result dictionary returned by run_solution, and prints compact validation output. |
| 6 | +""" |
| 7 | + |
| 8 | +import argparse |
| 9 | +import importlib |
| 10 | +import time |
| 11 | + |
| 12 | +import numpy as np |
| 13 | + |
| 14 | +DEFAULT_CONFIG = { |
| 15 | + "n_qubits": 12, |
| 16 | + "entangler_angle": 0.31, |
| 17 | + "true_p01": 0.034, |
| 18 | + "true_p10": 0.011, |
| 19 | + "initial_p01": 0.070, |
| 20 | + "initial_p10": 0.040, |
| 21 | + "max_steps": 120, |
| 22 | + "learning_rate": 0.04, |
| 23 | +} |
| 24 | + |
| 25 | +PROBE_NAMES = ("zeros", "ones", "neel", "plus") |
| 26 | + |
| 27 | + |
| 28 | +def asymmetric_bitflip_kraus_numpy(p01, p10): |
| 29 | + return [ |
| 30 | + np.array( |
| 31 | + [[np.sqrt(1.0 - p01), 0.0], [0.0, np.sqrt(1.0 - p10)]], |
| 32 | + dtype=np.complex128, |
| 33 | + ), |
| 34 | + np.array([[0.0, np.sqrt(p10)], [0.0, 0.0]], dtype=np.complex128), |
| 35 | + np.array([[0.0, 0.0], [np.sqrt(p01), 0.0]], dtype=np.complex128), |
| 36 | + ] |
| 37 | + |
| 38 | + |
| 39 | +def trace_preserving_error(p01, p10): |
| 40 | + kraus = asymmetric_bitflip_kraus_numpy(p01, p10) |
| 41 | + identity = np.eye(2, dtype=np.complex128) |
| 42 | + contraction = sum(k.conj().T @ k for k in kraus) |
| 43 | + return float(np.max(np.abs(contraction - identity))) |
| 44 | + |
| 45 | + |
| 46 | +def print_expectation_table(targets, fitted): |
| 47 | + observable_names = [f"Z{i}" for i in range(targets.shape[1] - 1)] + ["parity"] |
| 48 | + print("Expectation comparison:") |
| 49 | + for probe_index, probe_name in enumerate(PROBE_NAMES): |
| 50 | + for obs_index, obs_name in enumerate(observable_names): |
| 51 | + target = targets[probe_index, obs_index] |
| 52 | + value = fitted[probe_index, obs_index] |
| 53 | + print( |
| 54 | + f" {probe_name:15s} {obs_name:6s} " |
| 55 | + f"target={target: .8f} fitted={value: .8f} " |
| 56 | + f"abs_error={abs(target - value):.3e}" |
| 57 | + ) |
| 58 | + |
| 59 | + |
| 60 | +def evaluate(solution_module, config): |
| 61 | + module = importlib.import_module(solution_module) |
| 62 | + |
| 63 | + start = time.perf_counter() |
| 64 | + results = module.run_solution(config) |
| 65 | + elapsed = time.perf_counter() - start |
| 66 | + |
| 67 | + targets = np.asarray(results["target_expectations"], dtype=float) |
| 68 | + fitted = np.asarray(results["fitted_expectations"], dtype=float) |
| 69 | + expectation_errors = np.abs(fitted - targets) |
| 70 | + final_p01 = float(results["final_p01"]) |
| 71 | + final_p10 = float(results["final_p10"]) |
| 72 | + absolute_error_p01 = abs(final_p01 - config["true_p01"]) |
| 73 | + absolute_error_p10 = abs(final_p10 - config["true_p10"]) |
| 74 | + tp_error = trace_preserving_error(final_p01, final_p10) |
| 75 | + |
| 76 | + criteria = { |
| 77 | + "loss history length": len(results["loss_history"]) == config["max_steps"], |
| 78 | + "loss improves": float(results["final_loss"]) < float(results["initial_loss"]), |
| 79 | + "target shape": targets.shape == (len(PROBE_NAMES), config["n_qubits"] + 1), |
| 80 | + "fitted shape": fitted.shape == (len(PROBE_NAMES), config["n_qubits"] + 1), |
| 81 | + "p01 absolute error <= 1e-4": absolute_error_p01 <= 1e-4, |
| 82 | + "p10 absolute error <= 1e-4": absolute_error_p10 <= 1e-4, |
| 83 | + "trace preserving error <= 1e-8": tp_error <= 1e-8, |
| 84 | + } |
| 85 | + |
| 86 | + print("Challenge 4 evaluation") |
| 87 | + print(f"Solution module: {solution_module}") |
| 88 | + print(f"End-to-end solution time: {elapsed:.2f}s") |
| 89 | + print(f"True p01, p10: {config['true_p01']:.8f}, {config['true_p10']:.8f}") |
| 90 | + print( |
| 91 | + "Initial p01, p10: " |
| 92 | + f"{float(results['initial_p01']):.8f}, {float(results['initial_p10']):.8f}" |
| 93 | + ) |
| 94 | + print(f"Fitted p01, p10: {final_p01:.8f}, {final_p10:.8f}") |
| 95 | + print(f"Absolute p01 error: {absolute_error_p01:.8e}") |
| 96 | + print(f"Absolute p10 error: {absolute_error_p10:.8e}") |
| 97 | + print(f"Initial loss: {float(results['initial_loss']):.8e}") |
| 98 | + print(f"Final loss: {float(results['final_loss']):.8e}") |
| 99 | + print(f"Max expectation abs error: {float(np.max(expectation_errors)):.8e}") |
| 100 | + print(f"Mean expectation abs error: {float(np.mean(expectation_errors)):.8e}") |
| 101 | + print(f"Trace-preserving error: {tp_error:.8e}") |
| 102 | + print(f"Loss history length: {len(results['loss_history'])}") |
| 103 | + print(f"Returned NumPy keys: {sorted(results)}") |
| 104 | + print_expectation_table(targets, fitted) |
| 105 | + print("Passing criteria:") |
| 106 | + for name, passed in criteria.items(): |
| 107 | + print(f" {name}: {'PASS' if passed else 'FAIL'}") |
| 108 | + print(f"Overall: {'PASS' if all(criteria.values()) else 'FAIL'}") |
| 109 | + |
| 110 | + |
| 111 | +def main(): |
| 112 | + parser = argparse.ArgumentParser() |
| 113 | + parser.add_argument("--solution", default="solution_4") |
| 114 | + parser.add_argument("--max-steps", type=int, default=DEFAULT_CONFIG["max_steps"]) |
| 115 | + args = parser.parse_args() |
| 116 | + |
| 117 | + config = dict(DEFAULT_CONFIG) |
| 118 | + config["max_steps"] = args.max_steps |
| 119 | + evaluate(args.solution, config) |
| 120 | + |
| 121 | + |
| 122 | +if __name__ == "__main__": |
| 123 | + main() |
0 commit comments