microsoft/qdk
Publicmirrored from https://github.com/microsoft/qdkAvailable
source/pip/qsharp/interop/qiskit/backends/qsharp_backend.py
233lines · modecode
| 1 | # Copyright (c) Microsoft Corporation. |
| 2 | # Licensed under the MIT License. |
| 3 | |
| 4 | from collections import Counter |
| 5 | import logging |
| 6 | from typing import Any, Dict, List, Optional, Tuple, Union |
| 7 | from uuid import uuid4 |
| 8 | |
| 9 | from qiskit import QuantumCircuit |
| 10 | from qiskit.providers import Options |
| 11 | from qiskit.transpiler.target import Target |
| 12 | from .... import TargetProfile |
| 13 | from .. import OutputSemantics |
| 14 | from ..execution import DetaultExecutor |
| 15 | from ..jobs import QsSimJob |
| 16 | from .backend_base import BackendBase |
| 17 | from .compilation import Compilation |
| 18 | from .errors import Errors |
| 19 | |
| 20 | logger = logging.getLogger(__name__) |
| 21 | |
| 22 | |
| 23 | class QSharpBackend(BackendBase): |
| 24 | """ |
| 25 | A virtual backend for running Qiskit circuits using the Q# simulator. |
| 26 | """ |
| 27 | |
| 28 | # This init is included for the docstring |
| 29 | # pylint: disable=useless-parent-delegation |
| 30 | def __init__( |
| 31 | self, |
| 32 | target: Optional[Target] = None, |
| 33 | qiskit_pass_options: Optional[Dict[str, Any]] = None, |
| 34 | transpile_options: Optional[Dict[str, Any]] = None, |
| 35 | qasm_export_options: Optional[Dict[str, Any]] = None, |
| 36 | skip_transpilation: bool = False, |
| 37 | **options, |
| 38 | ): |
| 39 | """ |
| 40 | :param target: The target to use for the backend. |
| 41 | :param qiskit_pass_options: Options for the Qiskit passes. |
| 42 | :type qiskit_pass_options: Dict |
| 43 | :param transpile_options: Options for the transpiler. |
| 44 | :type transpile_options: Dict |
| 45 | :param qasm_export_options: Options for the QASM3 exporter. |
| 46 | :type qasm_export_options: Dict |
| 47 | :param skip_transpilation: Skip Qiskit transpilation. |
| 48 | :type skip_transpilation: bool |
| 49 | :param **options: Default option overrides. These can also be overridden per-call via |
| 50 | :meth:`run`. Common options: |
| 51 | |
| 52 | - ``name`` (str): The name of the circuit used as the entry point. Defaults to the circuit name. |
| 53 | - ``target_profile`` (TargetProfile): The target profile to use for the compilation. |
| 54 | - ``output_semantics`` (OutputSemantics): The output semantics for the compilation. |
| 55 | Defaults to ``OutputSemantics.Qiskit``. |
| 56 | - ``shots`` (int): The number of shots to run the program for. Defaults to ``1024``. |
| 57 | - ``seed`` (int): The seed to use for the random number generator. Defaults to ``None``. |
| 58 | - ``search_path`` (str): The path to search for imports. Defaults to ``'.'``. |
| 59 | - ``output_fn`` (Callable): A callback function to receive the output of the circuit. |
| 60 | Defaults to ``None``. |
| 61 | - ``executor``: The executor to be used to submit the job. Defaults to ``SynchronousExecutor``. |
| 62 | """ |
| 63 | |
| 64 | super().__init__( |
| 65 | target, |
| 66 | qiskit_pass_options, |
| 67 | transpile_options, |
| 68 | qasm_export_options, |
| 69 | skip_transpilation, |
| 70 | **options, |
| 71 | ) |
| 72 | |
| 73 | @classmethod |
| 74 | def _default_options(cls): |
| 75 | return Options( |
| 76 | name="program", |
| 77 | params=None, |
| 78 | search_path=".", |
| 79 | shots=1024, |
| 80 | seed=None, |
| 81 | output_fn=None, |
| 82 | target_profile=TargetProfile.Unrestricted, |
| 83 | output_semantics=OutputSemantics.Qiskit, |
| 84 | executor=DetaultExecutor(), |
| 85 | ) |
| 86 | |
| 87 | def run( |
| 88 | self, |
| 89 | run_input: Union[QuantumCircuit, List[QuantumCircuit]], |
| 90 | **options, |
| 91 | ) -> QsSimJob: |
| 92 | """ |
| 93 | Runs the given QuantumCircuit using the Q# simulator. |
| 94 | |
| 95 | :param run_input: The QuantumCircuit to be executed. |
| 96 | :param **options: Per-call option overrides. Common options: |
| 97 | |
| 98 | - ``name`` (str): The name of the circuit used as the entry point. Defaults to the circuit name. |
| 99 | - ``target_profile`` (TargetProfile): The target profile to use for the compilation. |
| 100 | - ``output_semantics`` (OutputSemantics): The output semantics for the compilation. |
| 101 | Defaults to ``OutputSemantics.Qiskit``. |
| 102 | - ``shots`` (int): The number of shots to run the program for. Defaults to ``1024``. |
| 103 | - ``seed`` (int): The seed to use for the random number generator. Defaults to ``None``. |
| 104 | - ``search_path`` (str): The path to search for imports. Defaults to ``'.'``. |
| 105 | - ``output_fn`` (Callable): A callback function to receive the output of the circuit. |
| 106 | Defaults to ``None``. |
| 107 | - ``executor``: The executor to be used to submit the job. Defaults to ``SynchronousExecutor``. |
| 108 | :return: The simulation job. |
| 109 | :rtype: QsSimJob |
| 110 | :raises QSharpError: If there is an error evaluating the source code. |
| 111 | :raises QasmError: If there is an error generating, parsing, or compiling QASM. |
| 112 | :raises ValueError: If run_input is not a QuantumCircuit or List[QuantumCircuit]. |
| 113 | """ |
| 114 | |
| 115 | run_input = self._validate_quantum_circuits(run_input) |
| 116 | return self._run(run_input, **options) |
| 117 | |
| 118 | def _execute(self, programs: List[Compilation], **input_params) -> Dict[str, Any]: |
| 119 | exec_results: List[Tuple[Compilation, Dict[str, Any]]] = [ |
| 120 | ( |
| 121 | program, |
| 122 | _run_qasm(program.qasm, vars(self.options).copy(), **input_params), |
| 123 | ) |
| 124 | for program in programs |
| 125 | ] |
| 126 | job_results = [] |
| 127 | |
| 128 | shots = input_params.get("shots") |
| 129 | if shots is None: |
| 130 | raise ValueError(str(Errors.MISSING_NUMBER_OF_SHOTS)) |
| 131 | |
| 132 | for program, exec_result in exec_results: |
| 133 | results = [self._shot_to_bitstring(result) for result in exec_result] |
| 134 | |
| 135 | counts = Counter(results) |
| 136 | counts_dict = dict(counts) |
| 137 | probabilities = { |
| 138 | bitstring: (count / shots) for bitstring, count in counts_dict.items() |
| 139 | } |
| 140 | |
| 141 | job_result = { |
| 142 | "data": {"counts": counts_dict, "probabilities": probabilities}, |
| 143 | "success": True, |
| 144 | "header": { |
| 145 | "metadata": {"qasm": program.qasm}, |
| 146 | "name": program.circuit.name, |
| 147 | "compilation_time_taken": program.time_taken, |
| 148 | }, |
| 149 | "shots": shots, |
| 150 | } |
| 151 | job_results.append(job_result) |
| 152 | |
| 153 | # All of these fields are required by the Result object |
| 154 | result_dict = { |
| 155 | "results": job_results, |
| 156 | "qobj_id": str(uuid4()), |
| 157 | "success": True, |
| 158 | } |
| 159 | |
| 160 | return result_dict |
| 161 | |
| 162 | |
| 163 | def _run_qasm( |
| 164 | qasm: str, |
| 165 | default_options: Options, |
| 166 | **options, |
| 167 | ) -> Any: |
| 168 | """ |
| 169 | Runs the supplied OpenQASM 3 program. |
| 170 | Gates defined by stdgates.inc will be overridden with definitions |
| 171 | from the Q# compiler. |
| 172 | |
| 173 | Any gates, such as matrix unitaries, that are not able to be |
| 174 | transpiled will result in an error. |
| 175 | |
| 176 | :param source: The input OpenQASM 3 string to be processed. |
| 177 | :param default_options: Default backend option values. |
| 178 | :param **options: Common options: |
| 179 | |
| 180 | - ``target_profile`` (TargetProfile): The target profile to use for the compilation. |
| 181 | - ``output_semantics`` (OutputSemantics): The output semantics for the compilation. |
| 182 | - ``name`` (str): The name of the circuit. Defaults to ``'program'``. |
| 183 | - ``search_path`` (str): The optional search path for resolving qasm imports. |
| 184 | - ``shots`` (int): The number of shots to run the program for. |
| 185 | - ``seed`` (int): The seed to use for the random number generator. |
| 186 | - ``output_fn`` (Callable): A callback for each output. Defaults to ``None``. |
| 187 | :return: A list of results or runtime errors. |
| 188 | :raises QSharpError: If there is an error evaluating the source code. |
| 189 | :raises QasmError: If there is an error generating, parsing, or compiling QASM. |
| 190 | """ |
| 191 | |
| 192 | from ...._native import run_qasm_program, Output # type: ignore |
| 193 | from ...._fs import read_file, list_directory, resolve |
| 194 | from ...._http import fetch_github |
| 195 | |
| 196 | def callback(output: Output) -> None: |
| 197 | print(output) |
| 198 | |
| 199 | output_fn = options.pop("output_fn", callback) |
| 200 | |
| 201 | def value_or_default(key: str) -> Any: |
| 202 | return options.pop(key, default_options[key]) |
| 203 | |
| 204 | # when passing the args into the rust layer, any kwargs with None values |
| 205 | # will cause an error, so we need to filter them out. |
| 206 | args = {} |
| 207 | if name := value_or_default("name"): |
| 208 | args["name"] = name |
| 209 | |
| 210 | if target_profile := value_or_default("target_profile"): |
| 211 | args["target_profile"] = target_profile |
| 212 | if output_semantics := value_or_default("output_semantics"): |
| 213 | args["output_semantics"] = output_semantics |
| 214 | |
| 215 | if search_path := value_or_default("search_path"): |
| 216 | args["search_path"] = search_path |
| 217 | if shots := value_or_default("shots"): |
| 218 | args["shots"] = shots |
| 219 | if seed := value_or_default("seed"): |
| 220 | args["seed"] = seed |
| 221 | |
| 222 | return run_qasm_program( |
| 223 | qasm, |
| 224 | output_fn, |
| 225 | None, |
| 226 | None, |
| 227 | None, |
| 228 | read_file, |
| 229 | list_directory, |
| 230 | resolve, |
| 231 | fetch_github, |
| 232 | **args, |
| 233 | ) |
| 234 | |