Skip to content

Commit a89d235

Browse files
authored
💡 added docstrings for raised errors (#405)
## Description This PR adds docstring documentation for raised errors. This resolves #143. ## Checklist: <!--- This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. --> - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality. - [x] I have updated the documentation to reflect these changes. - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes.
1 parent 940f179 commit a89d235

9 files changed

Lines changed: 130 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ This project adheres to [Semantic Versioning], with the exception that minor rel
99

1010
## [Unreleased]
1111

12+
### Added
13+
14+
- 📝 Added docstrings for raised errors for all methods ([#405]) ([**@nquetschlich**])
15+
1216
### Changed
1317

1418
- ✨ Improved the ML part and its usability ([#403]) ([**@nquetschlich**])
@@ -27,6 +31,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
2731

2832
<!-- PR links -->
2933

34+
[#405]: https://github.com/munich-quantum-toolkit/predictor/pull/405
3035
[#403]: https://github.com/munich-quantum-toolkit/predictor/pull/403
3136
[#401]: https://github.com/munich-quantum-toolkit/predictor/pull/401
3237
[#393]: https://github.com/munich-quantum-toolkit/predictor/pull/393

src/mqt/predictor/ml/predictor.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ def _compile_all_circuits_devicewise(
163163
path_uncompiled_circuits: The path to the directory containing the circuits to be compiled. Defaults to None.
164164
path_compiled_circuits: The path to the directory where the compiled circuits should be saved. Defaults to None.
165165
logger_level: The level of the logger. Defaults to logging.INFO.
166+
167+
Raises:
168+
RuntimeError: If an error occurs during compilation.
166169
"""
167170
logger.setLevel(logger_level)
168171

@@ -306,6 +309,9 @@ def _generate_training_sample(
306309
307310
Returns:
308311
Training_sample, circuit_name, scores
312+
313+
Raises:
314+
RuntimeError: If the file is not a qasm file or if no compiled circuits are found for the given file.
309315
"""
310316
logger.setLevel(logger_level)
311317

@@ -370,6 +376,9 @@ def train_random_forest_model(
370376
Returns:
371377
Either a trained RandomForestRegressor to estimate the Hellinger distance for a single device,
372378
or a trained RandomForestClassifier to score multiple devices according to a specific figure of merit.
379+
380+
Raises:
381+
ValueError: If the figure of merit is 'hellinger_distance' and more than one device is provided.
373382
"""
374383
tree_param = [
375384
{
@@ -404,7 +413,11 @@ def train_random_forest_model(
404413
return mdl.best_estimator_
405414

406415
def _get_prepared_training_data(self) -> TrainingData:
407-
"""Returns the training data for the given figure of merit."""
416+
"""Returns the training data for the given figure of merit.
417+
418+
Raises:
419+
FileNotFoundError: If the training data files are not found.
420+
"""
408421
with resources.as_file(get_path_training_data() / "training_data_aggregated") as path:
409422
prefix = f"{self.figure_of_merit}.npy"
410423
file_data = path / f"training_data_{prefix}"
@@ -451,6 +464,10 @@ def predict_device_for_figure_of_merit(
451464
452465
Returns:
453466
The probabilities for all supported quantum devices to be the most suitable one for the given quantum circuit.
467+
468+
Raises:
469+
FileNotFoundError: If the ML model is not trained yet.
470+
ValueError: If no suitable device is found for the given quantum circuit.
454471
"""
455472
if isinstance(qc, Path) and qc.exists():
456473
qc = QuantumCircuit.from_qasm_file(qc)

src/mqt/predictor/reward.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,19 @@ def expected_fidelity(qc: QuantumCircuit, device: Target, precision: int = 10) -
7373

7474

7575
def calc_qubit_index(qargs: list[Qubit], qregs: list[QuantumRegister], index: int) -> int:
76-
"""Calculates the global qubit index for a given quantum circuit and qubit index."""
76+
"""Calculates the global qubit index for a given quantum circuit and qubit index.
77+
78+
Arguments:
79+
qargs: The qubits of the quantum circuit.
80+
qregs: The quantum registers of the quantum circuit.
81+
index: The index of the qubit in the qargs list.
82+
83+
Returns:
84+
The global qubit index of the given qubit in the quantum circuit.
85+
86+
Raises:
87+
ValueError: If the qubit index is not found in the quantum registers.
88+
"""
7789
offset = 0
7890
for reg in qregs:
7991
if qargs[index] not in reg:
@@ -188,7 +200,17 @@ def estimated_success_probability(qc: QuantumCircuit, device: Target, precision:
188200

189201

190202
def esp_data_available(device: Target) -> bool:
191-
"""Check if calibration data to calculate ESP is available for the device."""
203+
"""Check if calibration data to calculate ESP is available for the device.
204+
205+
Arguments:
206+
device: The device to be checked for calibration data.
207+
208+
Returns:
209+
True if all required calibration data is available, False otherwise.
210+
211+
Raises:
212+
ValueError: If any required calibration data is missing or invalid.
213+
"""
192214
single_qubit_gates = set()
193215
two_qubit_gates = set()
194216

src/mqt/predictor/rl/actions.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,11 @@ class DeviceDependentAction(Action):
151151

152152

153153
def register_action(action: Action) -> Action:
154-
"""Registers a new action in the global actions registry."""
154+
"""Registers a new action in the global actions registry.
155+
156+
Raises:
157+
ValueError: If an action with the same name is already registered.
158+
"""
155159
if action.name in _ACTIONS:
156160
msg = f"Action with name {action.name} already registered."
157161
raise ValueError(msg)
@@ -160,7 +164,11 @@ def register_action(action: Action) -> Action:
160164

161165

162166
def remove_action(name: str) -> None:
163-
"""Removes an action from the global actions registry by name."""
167+
"""Removes an action from the global actions registry by name.
168+
169+
Raises:
170+
ValueError: If no action with the given name is registered.
171+
"""
164172
if name not in _ACTIONS:
165173
msg = f"No action with name {name} is registered."
166174
raise KeyError(msg)

src/mqt/predictor/rl/helper.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ def get_state_sample(max_qubits: int, path_training_circuits: Path, rng: Generat
3939
4040
Returns:
4141
A tuple containing the random quantum circuit and the path to the file from which it was read.
42+
43+
Raises:
44+
RuntimeError: If no quantum circuit could be read from the training circuits folder.
4245
"""
4346
file_list = list(path_training_circuits.glob("*.qasm"))
4447

src/mqt/predictor/rl/parsing.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ def get_bqskit_native_gates(device: Target) -> list[gates.Gate]:
9494
9595
Returns:
9696
The native gates of the given device as BQSKit gates.
97+
98+
Raises:
99+
ValueError: If a gate in the device is not supported in BQSKit.
97100
"""
98101
gate_map = {
99102
# --- 1-qubit gates ---

src/mqt/predictor/rl/predictor.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ def compile_as_predicted(
5959
6060
Returns:
6161
A tuple containing the compiled quantum circuit and the compilation information. If compilation fails, False is returned.
62+
63+
Raises:
64+
RuntimeError: If an error occurs during compilation.
6265
"""
6366
trained_rl_model = load_model("model_" + self.figure_of_merit + "_" + self.device_name)
6467

@@ -134,6 +137,9 @@ def load_model(model_name: str) -> MaskablePPO:
134137
135138
Returns:
136139
The loaded model.
140+
141+
Raises:
142+
FileNotFoundError: If the model file does not exist.
137143
"""
138144
path = get_path_trained_model()
139145
if Path(path / (model_name + ".zip")).is_file():
@@ -160,6 +166,9 @@ def rl_compile(
160166
161167
Returns:
162168
A tuple containing the compiled quantum circuit and the compilation information. If compilation fails, False is returned.
169+
170+
Raises:
171+
ValueError: If figure_of_merit or device is None and predictor_singleton is also None.
163172
"""
164173
if predictor_singleton is None:
165174
if figure_of_merit is None:

src/mqt/predictor/rl/predictorenv.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,16 @@ def __init__(
7373
reward_function: figure_of_merit = "expected_fidelity",
7474
path_training_circuits: Path | None = None,
7575
) -> None:
76-
"""Initializes the PredictorEnv object."""
76+
"""Initializes the PredictorEnv object.
77+
78+
Arguments:
79+
device: The target device to be used for compilation.
80+
reward_function: The figure of merit to be used for the reward function. Defaults to "expected_fidelity".
81+
path_training_circuits: The path to the training circuits folder. Defaults to None, which uses the default path.
82+
83+
Raises:
84+
ValueError: If the reward function is "estimated_success_probability" and no calibration data is available for the device or if the reward function is "estimated_hellinger_distance" and no trained model is available for the device.
85+
"""
7786
logger.info("Init env: " + reward_function)
7887

7988
self.path_training_circuits = path_training_circuits or get_path_training_circuits()
@@ -156,7 +165,17 @@ def __init__(
156165
self.filename = ""
157166

158167
def step(self, action: int) -> tuple[dict[str, Any], float, bool, bool, dict[Any, Any]]:
159-
"""Executes the given action and returns the new state, the reward, whether the episode is done, whether the episode is truncated and additional information."""
168+
"""Executes the given action and returns the new state, the reward, whether the episode is done, whether the episode is truncated and additional information.
169+
170+
Arguments:
171+
action: The action to be executed, represented by its index in the action set.
172+
173+
Returns:
174+
A tuple containing the new state as a feature dictionary, the reward value, whether the episode is done, whether the episode is truncated, and additional information.
175+
176+
Raises:
177+
RuntimeError: If no valid actions are left.
178+
"""
160179
self.used_actions.append(str(self.action_set[action].name))
161180
altered_qc = self.apply_action(action)
162181
if not altered_qc:
@@ -271,7 +290,17 @@ def action_masks(self) -> list[bool]:
271290
return action_mask
272291

273292
def apply_action(self, action_index: int) -> QuantumCircuit | None:
274-
"""Applies the given action to the current state and returns the altered state."""
293+
"""Applies the given action to the current state and returns the altered state.
294+
295+
Arguments:
296+
action_index: The index of the action to be applied, which must be in the action set.
297+
298+
Returns:
299+
The altered quantum circuit after applying the action, or None if the action is to terminate the compilation.
300+
301+
Raises:
302+
ValueError: If the action index is not in the action set or if the action origin is not supported.
303+
"""
275304
if action_index not in self.action_set:
276305
msg = f"Action {action_index} not supported."
277306
raise ValueError(msg)
@@ -358,6 +387,18 @@ def _apply_tket_action(self, action: Action, action_index: int) -> QuantumCircui
358387
return altered_qc
359388

360389
def _apply_bqskit_action(self, action: Action, action_index: int) -> QuantumCircuit:
390+
"""Applies the given BQSKit action to the current state and returns the altered state.
391+
392+
Arguments:
393+
action: The BQSKit action to be applied.
394+
action_index: The index of the action in the action set.
395+
396+
Returns:
397+
The altered quantum circuit after applying the action.
398+
399+
Raises:
400+
ValueError: If the action index is not in the action set or if the action origin is not supported.
401+
"""
361402
bqskit_qc = qiskit_to_bqskit(self.state)
362403
assert callable(action.transpile_pass)
363404
if action_index in self.actions_opt_indices:

src/mqt/predictor/utils.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,20 @@ def timeout_watcher(
3737
args: list[QuantumCircuit | figure_of_merit | str | RL_Predictor],
3838
timeout: int,
3939
) -> tuple[QuantumCircuit, list[str]] | bool:
40-
"""Method that stops a function call after a given timeout limit."""
40+
"""Method that stops a function call after a given timeout limit.
41+
42+
Arguments:
43+
func: The function to be called.
44+
args: The arguments to be passed to the function.
45+
timeout: The timeout limit in seconds.
46+
47+
Returns:
48+
The result of the function call if it finishes within the timeout limit, otherwise False.
49+
50+
Raises:
51+
RuntimeWarning: If the timeout is not supported on the current platform (e.g., Windows).
52+
TimeoutExceptionError: If the function call exceeds the timeout limit.
53+
"""
4154
if sys.platform == "win32":
4255
warn("Timeout is not supported on Windows.", category=RuntimeWarning, stacklevel=2)
4356
return func(*args) if isinstance(args, tuple | list) else func(args)

0 commit comments

Comments
 (0)