Skip to content

Commit

Permalink
Rl improvements (#61)
Browse files Browse the repository at this point in the history
This PR provides several improvements:

- Changed the observation space representation from a `Box` one to a
`Dict` where each feature is represented in a more suitable way.
- 500 instead of 200 training circuits are used.
- Trained models are not part of the repository itself anymore, but are
downloaded when needed at runtime.
- Two more reward functions are introduced and evaluated.
- Enhancing small code issues, such as variable naming.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
nquetschlich and pre-commit-ci[bot] authored Jan 31, 2023
1 parent 814e37f commit 5a3404d
Show file tree
Hide file tree
Showing 28 changed files with 1,120 additions and 557 deletions.
200 changes: 147 additions & 53 deletions notebooks/rl/evaluation.ipynb

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added notebooks/rl/results/gate_ratio_hist.pdf
Binary file not shown.
Binary file added notebooks/rl/results/mix_bench.pdf
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ requires-python = ">=3.8"
dynamic = ["version"]

dependencies = [
"mqt.bench>=0.2.0",
"mqt.bench>=0.3.0",
"sb3_contrib>=1.6.2",
"scikit-learn>=1.0.2",
"importlib_resources>=5.0; python_version < '3.10'",
Expand Down
3 changes: 1 addition & 2 deletions src/mqt/predictor/Calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ def __init__(self):
self.ionq_calibration = parse_ionq_calibration_config()

except Exception as e:
print("Calibration init failed: ", e)
return None
raise RuntimeError("Error in Calibration initialization: " + str(e)) from e


def get_mean_IBM_washington_cx_error():
Expand Down
50 changes: 30 additions & 20 deletions src/mqt/predictor/ml/Predictor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import glob
import logging
from pathlib import Path

import matplotlib.pyplot as plt
Expand All @@ -16,7 +17,17 @@


class Predictor:
def __init__(self):
def __init__(self, verbose=0):
self.verbose = verbose

self.logger = logging.getLogger("mqtpredictor")
if verbose == 1:
self.logger.setLevel(logging.INFO)
elif verbose == 2:
self.logger.setLevel(logging.DEBUG)
else:
self.logger.setLevel(logging.WARNING)

self.clf = None

def set_classifier(self, clf):
Expand Down Expand Up @@ -47,7 +58,7 @@ def compile_all_circuits_for_qc(
if target_path is None:
target_path = str(ml.helper.get_path_training_circuits_compiled())

print("compile_all_circuits_for_qc:", filename)
self.logger.info("Processing: " + filename)
qc = QuantumCircuit.from_qasm_file(Path(source_path) / filename)

if not qc:
Expand Down Expand Up @@ -113,13 +124,14 @@ def compile_all_circuits_for_qc(
continue

if all(x is False for x in results):
print("No compilation succeeded for this quantum circuit.")
self.logger.debug(
"No compilation succeeded for this quantum circuit: " + filename
)
return False
return True

except Exception as e:
print("fail: ", e)
return False
raise RuntimeError("Error during compilation: " + str(e)) from e

def generate_compiled_circuits(
self,
Expand Down Expand Up @@ -242,7 +254,7 @@ def generate_training_sample(
return False

LUT = ml.helper.get_index_to_comppath_LUT()
print("Checking ", file)
self.logger.debug("Checking " + file)
scores = []
for _ in range(len(LUT)):
scores.append([])
Expand Down Expand Up @@ -309,18 +321,20 @@ def train_random_forest_classifier(self, visualize_results=False):
res, _ = self.calc_performance_measures(scores_filtered, y_pred, y_test)
self.plot_eval_histogram(res, filename="RandomForestClassifier")

print("Best Accuracy: ", clf.best_score_)
self.logger.info("Best Accuracy: " + clf.best_score_)
top3 = (res.count(1) + res.count(2) + res.count(3)) / len(res)
print("Top 3: ", top3)
print("Feature Importance: ", clf.best_estimator_.feature_importances_)
self.logger.info("Top 3: " + top3)
self.logger.info(
"Feature Importance: " + clf.best_estimator_.feature_importances_
)

self.plot_eval_all_detailed_compact_normed(
names_filtered, scores_filtered, y_pred, y_test
)

self.set_classifier(clf.best_estimator_)
ml.helper.save_classifier(clf.best_estimator_)
print("Random Forest classifier is trained and saved.")
self.logger.info("Random Forest classifier is trained and saved.")

return self.clf is not None

Expand Down Expand Up @@ -504,8 +518,7 @@ def predict(self, qasm_str_or_path: str):
if path.is_file():
self.clf = load(str(path))
else:
print("Fail: Classifier is neither trained nor saved!")
return None
raise FileNotFoundError("Classifier is neither trained nor saved.")

feature_dict = ml.helper.create_feature_dict(qasm_str_or_path)
if not feature_dict:
Expand All @@ -524,18 +537,16 @@ def compile_as_predicted(self, qc: str, prediction: int):

LUT = ml.helper.get_index_to_comppath_LUT()
if prediction < 0 or prediction >= len(LUT):
print("Provided prediction is faulty.")
return None
raise IndexError("Prediction index is out of range.")
if not isinstance(qc, QuantumCircuit):
if Path(qc).exists():
print("Reading from .qasm path: ", qc)
self.logger.info("Reading from .qasm path: " + qc)
qc = QuantumCircuit.from_qasm_file(qc)
elif QuantumCircuit.from_qasm_str(qc):
print("Reading from .qasm str")
self.logger.info("Reading from .qasm str")
qc = QuantumCircuit.from_qasm_str(qc)
else:
print("Neither a qasm file path nor a qasm str has been provided.")
return False
raise ValueError("Invalid 'qc' parameter value.")

prediction_information = LUT.get(prediction)
gate_set_name = prediction_information[0]
Expand Down Expand Up @@ -563,8 +574,7 @@ def compile_as_predicted(self, qc: str, prediction: int):
ml.helper.get_index_to_comppath_LUT()[prediction],
)
else:
print("Error: Compiler not found.")
return False
raise ValueError("Invalid compiler name.")

def instantiate_supervised_ML_model(self, timeout):
# Generate compiled circuits and save them as qasm files
Expand Down
6 changes: 4 additions & 2 deletions src/mqt/predictor/ml/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
else:
from importlib import resources

import logging
from pathlib import Path

import numpy as np
Expand All @@ -15,6 +16,8 @@

from mqt.predictor import ml, utils

logger = logging.getLogger("mqtpredictor")


def qcompile(qc: QuantumCircuit | str) -> QuantumCircuit:
"""Returns the compiled quantum circuit which is compiled with the predicted combination of compilation options.
Expand Down Expand Up @@ -163,8 +166,7 @@ def create_feature_dict(qc: str):
elif "OPENQASM" in qc:
qc = QuantumCircuit.from_qasm_str(qc)
else:
print("Neither a qasm file path nor a qasm str has been provided.")
return False
raise ValueError("Invalid input for 'qc' parameter.") from None

ops_list = qc.count_ops()
feature_dict = dict_to_featurevector(ops_list)
Expand Down
82 changes: 55 additions & 27 deletions src/mqt/predictor/reward.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import logging

import numpy as np
from qiskit import QuantumCircuit

from mqt.predictor import Calibration
Expand All @@ -7,38 +10,52 @@
get_rigetti_qubit_dict,
)

logger = logging.getLogger("mqtpredictor")


def crit_depth(qc):
def crit_depth(qc: QuantumCircuit, precision: int = 10):
(
program_communication,
critical_depth,
entanglement_ratio,
parallelism,
liveness,
) = calc_supermarq_features(qc)
return 1 - critical_depth
return np.round(1 - critical_depth, precision)


def parallelism(qc):
def parallelism(qc: QuantumCircuit, precision: int = 10):
(
program_communication,
critical_depth,
entanglement_ratio,
parallelism,
liveness,
) = calc_supermarq_features(qc)
return critical_depth
return np.round(1 - parallelism, precision)


def gate_ratio(qc: QuantumCircuit, precision: int = 10):
return np.round(1 - qc.num_nonlocal_gates() / qc.size(), precision)


def mix(qc: QuantumCircuit, device: str, precision: int = 10):
return (
expected_fidelity(qc, device, precision) * 0.5 + crit_depth(qc, precision) * 0.5
)

def expected_fidelity(qc_or_path: str, device: str):

def expected_fidelity(qc_or_path: str, device: str, precision: int = 10):
if isinstance(qc_or_path, QuantumCircuit):
qc = qc_or_path
else:
try:
qc = QuantumCircuit.from_qasm_file(qc_or_path)
except Exception as e:
print("Fail in reward_expected_fidelity reading a the quantum circuit: ", e)
return 0
except Exception:
raise RuntimeError(
"Could not read QuantumCircuit from: " + qc_or_path
) from None

res = 1

calibration = Calibration.Calibration()
Expand Down Expand Up @@ -67,14 +84,19 @@ def expected_fidelity(qc_or_path: str, device: str):
gate_type, [first_qubit]
)
except Exception as e:
print(instruction, qargs)
print(
"Error in IBM backend.gate_error(): ",
e,
device,
first_qubit,
)
return 0
raise RuntimeError(
"Error in IBM backend.gate_error(): "
+ ", "
+ str(e)
+ ", "
+ device
+ ", "
+ first_qubit
+ ", "
+ instruction
+ ", "
+ qargs
) from None
else:
second_qubit = calc_qubit_index(qargs, qc.qregs, 1)
try:
Expand All @@ -84,15 +106,21 @@ def expected_fidelity(qc_or_path: str, device: str):
if specific_error == 1:
specific_error = calibration.ibm_washington_cx_mean_error
except Exception as e:
print(instruction, qargs)
print(
"Error in IBM backend.gate_error(): ",
e,
device,
first_qubit,
second_qubit,
)
return 0
raise RuntimeError(
"Error in IBM backend.gate_error(): "
+ ", "
+ str(e)
+ ", "
+ device
+ ", "
+ first_qubit
+ ", "
+ second_qubit
+ ", "
+ instruction
+ ", "
+ qargs
) from None

res *= 1 - float(specific_error)
elif "oqc_lucy" in device:
Expand Down Expand Up @@ -185,6 +213,6 @@ def expected_fidelity(qc_or_path: str, device: str):
res *= specific_fidelity

else:
print("Error: No suitable backend found!")
raise ValueError("Device not supported")

return res
return np.round(res, precision)
Loading

0 comments on commit 5a3404d

Please sign in to comment.