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