quantuminspire 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantuminspire/__init__.py +0 -0
- quantuminspire/cli/__init__.py +0 -0
- quantuminspire/cli/command_list.py +202 -0
- quantuminspire/py.typed +0 -0
- quantuminspire/sdk/__init__.py +0 -0
- quantuminspire/sdk/models/__init__.py +0 -0
- quantuminspire/sdk/models/base_algorithm.py +64 -0
- quantuminspire/sdk/models/circuit.py +79 -0
- quantuminspire/sdk/models/cqasm_algorithm.py +20 -0
- quantuminspire/sdk/models/file_algorithm.py +34 -0
- quantuminspire/sdk/models/hybrid_algorithm.py +20 -0
- quantuminspire/sdk/models/job_options.py +15 -0
- quantuminspire/sdk/quantum_interface.py +37 -0
- quantuminspire/util/__init__.py +0 -0
- quantuminspire/util/api/__init__.py +0 -0
- quantuminspire/util/api/base_backend.py +35 -0
- quantuminspire/util/api/local_backend.py +110 -0
- quantuminspire/util/api/quantum_interface.py +40 -0
- quantuminspire/util/api/remote_backend.py +225 -0
- quantuminspire/util/authentication.py +127 -0
- quantuminspire/util/configuration.py +217 -0
- quantuminspire-3.0.0.dist-info/LICENSE.md +177 -0
- quantuminspire-3.0.0.dist-info/METADATA +92 -0
- quantuminspire-3.0.0.dist-info/RECORD +26 -0
- quantuminspire-3.0.0.dist-info/WHEEL +4 -0
- quantuminspire-3.0.0.dist-info/entry_points.txt +3 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Module containing the commands for the Quantum Inspire CLI."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import webbrowser
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from compute_api_client import JobStatus
|
|
10
|
+
from rich import print
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
from typer import Typer
|
|
14
|
+
|
|
15
|
+
from quantuminspire.sdk.models.cqasm_algorithm import CqasmAlgorithm
|
|
16
|
+
from quantuminspire.sdk.models.file_algorithm import FileAlgorithm
|
|
17
|
+
from quantuminspire.sdk.models.hybrid_algorithm import HybridAlgorithm
|
|
18
|
+
from quantuminspire.sdk.models.job_options import JobOptions
|
|
19
|
+
from quantuminspire.util.api.local_backend import LocalBackend
|
|
20
|
+
from quantuminspire.util.api.remote_backend import RemoteBackend
|
|
21
|
+
from quantuminspire.util.authentication import OauthDeviceSession
|
|
22
|
+
from quantuminspire.util.configuration import Settings, Url
|
|
23
|
+
|
|
24
|
+
app = Typer(add_completion=False, no_args_is_help=True)
|
|
25
|
+
backend_types_app = Typer(no_args_is_help=True)
|
|
26
|
+
app.add_typer(backend_types_app, name="backends", help="Manage backends")
|
|
27
|
+
files_app = Typer(no_args_is_help=True)
|
|
28
|
+
app.add_typer(files_app, name="files", help="Manage files")
|
|
29
|
+
results_app = Typer(no_args_is_help=True)
|
|
30
|
+
app.add_typer(results_app, name="results", help="Manage results")
|
|
31
|
+
final_results_app = Typer(no_args_is_help=True)
|
|
32
|
+
app.add_typer(final_results_app, name="final_results", help="Manage final results")
|
|
33
|
+
|
|
34
|
+
console = Console()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@backend_types_app.command("list")
|
|
38
|
+
def list_backend_types() -> None:
|
|
39
|
+
"""List backends.
|
|
40
|
+
|
|
41
|
+
List all available backends and their configuration.
|
|
42
|
+
"""
|
|
43
|
+
backend = RemoteBackend()
|
|
44
|
+
backend_types = backend.get_backend_types().items
|
|
45
|
+
table = Table("id", "name", "status", "is_hardware", "supports_raw_data", "number_of_qubits", "max_number_of_shots")
|
|
46
|
+
for backend_type in backend_types:
|
|
47
|
+
table.add_row(
|
|
48
|
+
str(backend_type.id),
|
|
49
|
+
backend_type.name,
|
|
50
|
+
str(backend_type.status.name),
|
|
51
|
+
str(backend_type.is_hardware),
|
|
52
|
+
str(backend_type.supports_raw_data),
|
|
53
|
+
str(backend_type.nqubits),
|
|
54
|
+
str(backend_type.max_number_of_shots),
|
|
55
|
+
)
|
|
56
|
+
console.print(table)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_algorithm_from_file(file_path: Path) -> FileAlgorithm:
|
|
60
|
+
"""Load an algorithm from a file."""
|
|
61
|
+
if file_path.suffix == ".py":
|
|
62
|
+
algorithm: FileAlgorithm = HybridAlgorithm("", str(file_path))
|
|
63
|
+
elif file_path.suffix == ".cq":
|
|
64
|
+
algorithm = CqasmAlgorithm("", str(file_path))
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError(f"Unsupported file type: {file_path.suffix}. Supported types are .py and .cq")
|
|
67
|
+
|
|
68
|
+
algorithm.read_file(file_path)
|
|
69
|
+
return algorithm
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@files_app.command("upload")
|
|
73
|
+
def upload_files(
|
|
74
|
+
name: str = typer.Argument(..., help="The name of the file to upload"),
|
|
75
|
+
backend_type_id: int = typer.Argument(
|
|
76
|
+
..., help="The id of the backend type on which this algorithm should be executed"
|
|
77
|
+
),
|
|
78
|
+
num_shots: int = typer.Option(
|
|
79
|
+
1024, help="The number of shots to run the algorithm (only for pure cQASM algorithms)"
|
|
80
|
+
),
|
|
81
|
+
store_raw_data: bool = typer.Option(
|
|
82
|
+
False, help="Whether to enable shot memory for the algorithm (only for pure cQASM algorithms)"
|
|
83
|
+
),
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Upload a file to the QI API.
|
|
86
|
+
|
|
87
|
+
Upload a file containing either a Hybrid (.py) or a Quantum (.cq) algorithm, and run it on the QI platform.
|
|
88
|
+
"""
|
|
89
|
+
backend = RemoteBackend()
|
|
90
|
+
program = load_algorithm_from_file(Path(name))
|
|
91
|
+
program.read_file(Path(name))
|
|
92
|
+
job_options = JobOptions(number_of_shots=num_shots, raw_data_enabled=store_raw_data)
|
|
93
|
+
job_id = backend.run(program, backend_type_id=backend_type_id, options=job_options)
|
|
94
|
+
typer.echo(f"Upload file with name: {name}")
|
|
95
|
+
typer.echo(f"job_id {job_id}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@files_app.command("run")
|
|
99
|
+
def run_file(
|
|
100
|
+
name: str = typer.Argument(..., help="The full path of the file to run"),
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Run a file locally.
|
|
103
|
+
|
|
104
|
+
Run a Hybrid Quantum/Classical Algorithm locally. The quantum part will be run with QXEmulator.
|
|
105
|
+
"""
|
|
106
|
+
algorithm = HybridAlgorithm("test", "test")
|
|
107
|
+
algorithm.read_file(Path(name))
|
|
108
|
+
|
|
109
|
+
local_backend = LocalBackend()
|
|
110
|
+
job_id = local_backend.run(algorithm, 0)
|
|
111
|
+
results = local_backend.get_results(job_id)
|
|
112
|
+
typer.echo(f"{results}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _has_job_failed(backend: RemoteBackend, job_id: int) -> None:
|
|
116
|
+
"""Check if a job has failed and exit with error_code 1 if it has."""
|
|
117
|
+
job = backend.get_job(job_id)
|
|
118
|
+
if job.status == JobStatus.FAILED:
|
|
119
|
+
job.message = job.message or "Job failed."
|
|
120
|
+
typer.echo(job.message, err=True)
|
|
121
|
+
typer.echo(f"Trace id: {job.trace_id}", err=True)
|
|
122
|
+
raise typer.Exit(1)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@results_app.command("get")
|
|
126
|
+
def get_results(job_id: int = typer.Argument(..., help="The id of the run")) -> None:
|
|
127
|
+
"""Retrieve the results for a run.
|
|
128
|
+
|
|
129
|
+
Takes the id as returned by upload_files and retrieves the results for that run, if it's finished.
|
|
130
|
+
"""
|
|
131
|
+
backend = RemoteBackend()
|
|
132
|
+
_has_job_failed(backend, job_id)
|
|
133
|
+
results = backend.get_results(job_id)
|
|
134
|
+
|
|
135
|
+
if results is None:
|
|
136
|
+
typer.echo("No results.")
|
|
137
|
+
raise typer.Exit(1)
|
|
138
|
+
|
|
139
|
+
typer.echo("Raw results:")
|
|
140
|
+
print(results.model_dump())
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@final_results_app.command("get")
|
|
144
|
+
def get_final_results(job_id: int = typer.Argument(..., help="The id of the run")) -> None:
|
|
145
|
+
"""Retrieve the final results for a run.
|
|
146
|
+
|
|
147
|
+
Takes the id as returned by upload_files and retrieves the final results for that job, if it's finished.
|
|
148
|
+
"""
|
|
149
|
+
backend = RemoteBackend()
|
|
150
|
+
_has_job_failed(backend, job_id)
|
|
151
|
+
results = backend.get_final_results(job_id)
|
|
152
|
+
|
|
153
|
+
if results is None:
|
|
154
|
+
typer.echo("No final results.")
|
|
155
|
+
raise typer.Exit(1)
|
|
156
|
+
|
|
157
|
+
typer.echo("Raw final results:")
|
|
158
|
+
print(results.model_dump())
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@app.command("login")
|
|
162
|
+
def login(
|
|
163
|
+
host: Optional[str] = typer.Argument(None, help="The URL of the platform to which to connect"),
|
|
164
|
+
override_auth_config: bool = typer.Option(
|
|
165
|
+
False,
|
|
166
|
+
help="Will ignore authentication configuration suggested by the API and use stored configuration instead",
|
|
167
|
+
),
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Log in to Quantum Inspire.
|
|
170
|
+
|
|
171
|
+
Log in to the Quantum Inspire platform. The host can be overwritten, so that the user can connect to different
|
|
172
|
+
instances. If no host is specified, the production environment will be used.
|
|
173
|
+
"""
|
|
174
|
+
settings = Settings()
|
|
175
|
+
host = host or settings.default_host
|
|
176
|
+
host_url = Url(host)
|
|
177
|
+
settings.default_host = host_url
|
|
178
|
+
|
|
179
|
+
if not override_auth_config:
|
|
180
|
+
asyncio.run(settings.fetch_auth_settings(host_url))
|
|
181
|
+
|
|
182
|
+
auth_session = OauthDeviceSession(settings.auths[host_url])
|
|
183
|
+
|
|
184
|
+
login_info = auth_session.initialize_authorization()
|
|
185
|
+
typer.echo(f"Please continue logging in by opening: {login_info['verification_uri_complete']} in your browser")
|
|
186
|
+
typer.echo(f"If promped to verify a code, please confirm it is as follows: {login_info['user_code']}")
|
|
187
|
+
webbrowser.open(login_info["verification_uri_complete"], new=2)
|
|
188
|
+
tokens = auth_session.poll_for_tokens()
|
|
189
|
+
settings.store_tokens(host_url, tokens)
|
|
190
|
+
typer.echo("Login successful!")
|
|
191
|
+
typer.echo(f"Using member ID {settings.auths[host].team_member_id}")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@app.command("set-default-host")
|
|
195
|
+
def set_default_host(
|
|
196
|
+
host: str = typer.Argument(help="The URL of the platform to which to connect"),
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Set default_host for interacting with Quantum Inspire."""
|
|
199
|
+
settings = Settings()
|
|
200
|
+
settings.default_host = Url(host)
|
|
201
|
+
settings.write_settings_to_file()
|
|
202
|
+
typer.echo(f"Default host set to {host}")
|
quantuminspire/py.typed
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Module containing the Abstract Base Class for Algorithm classes."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseAlgorithm(ABC):
|
|
7
|
+
"""A container object, used to store content and metadata for the algorithm."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, platform_name: str, program_name: str) -> None:
|
|
10
|
+
self._platform_name = platform_name
|
|
11
|
+
self._program_name = program_name
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def program_name(self) -> str:
|
|
15
|
+
"""Return the name of the program.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
The string representation of the program name.
|
|
19
|
+
"""
|
|
20
|
+
return self._program_name
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def platform_name(self) -> str:
|
|
24
|
+
"""Return the name of the platform the algorithm is intended to run on.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The string representation of the platform name.
|
|
28
|
+
"""
|
|
29
|
+
return self._platform_name
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def content(self) -> str:
|
|
34
|
+
"""Return the content.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The string representation of the program.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def content_type(self) -> str:
|
|
43
|
+
"""Return the content type.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
The string representation of the `AlgorithmType`.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def compile_stage(self) -> str:
|
|
52
|
+
"""Return the compile stage.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The string representation of the `CompileStage`.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def language_name(self) -> str:
|
|
61
|
+
"""The name of the language the algorithm in the file is written in.
|
|
62
|
+
|
|
63
|
+
Should match a language in the API.
|
|
64
|
+
"""
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Module containing the Quantum Circuit class."""
|
|
2
|
+
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
from typing import Optional, Type
|
|
5
|
+
|
|
6
|
+
from compute_api_client import AlgorithmType, CompileStage
|
|
7
|
+
from opensquirrel import CircuitBuilder
|
|
8
|
+
|
|
9
|
+
from quantuminspire.sdk.models.base_algorithm import BaseAlgorithm
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Circuit(BaseAlgorithm):
|
|
13
|
+
"""A container object, interacting with OpenSquirrel and storing cQASM internally.
|
|
14
|
+
|
|
15
|
+
A circuit wraps OpenSquirrel to handle the boilerplate code for the CircuitBuilder.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self, platform_name: str, program_name: str, number_of_qubits: int, bit_registers: Optional[int] = None
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(platform_name, program_name)
|
|
22
|
+
self._number_of_qubits = number_of_qubits
|
|
23
|
+
if bit_registers is None:
|
|
24
|
+
bit_registers = number_of_qubits
|
|
25
|
+
self._number_of_bit_registers = bit_registers
|
|
26
|
+
self.ir = CircuitBuilder(qubit_register_size=number_of_qubits, bit_register_size=bit_registers)
|
|
27
|
+
self._cqasm: str = ""
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def content(self) -> str:
|
|
31
|
+
return self._cqasm
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def content_type(self) -> AlgorithmType:
|
|
35
|
+
return AlgorithmType.QUANTUM
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def compile_stage(self) -> CompileStage:
|
|
39
|
+
return CompileStage.NONE
|
|
40
|
+
|
|
41
|
+
def __enter__(self) -> "Circuit":
|
|
42
|
+
self.initialize()
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def __exit__(
|
|
46
|
+
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
|
|
47
|
+
) -> bool:
|
|
48
|
+
self.finalize()
|
|
49
|
+
return exc_type is None
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def language_name(self) -> str:
|
|
53
|
+
return "cQASM"
|
|
54
|
+
|
|
55
|
+
def initialize(self) -> None:
|
|
56
|
+
"""Initialize the quantum circuit."""
|
|
57
|
+
|
|
58
|
+
def finalize(self) -> None:
|
|
59
|
+
"""Finalize the quantum circuit.
|
|
60
|
+
|
|
61
|
+
After finishing writing the quantum circuit various actions are performed to generate the actual cQASM circuit.
|
|
62
|
+
The circuit is converted to cQASM. Due to some differences between OpenSquirrel and QX, the casing of the gates
|
|
63
|
+
need to be tweaked.
|
|
64
|
+
"""
|
|
65
|
+
self._cqasm = str(self.ir.to_circuit())
|
|
66
|
+
self._cqasm = (
|
|
67
|
+
self._cqasm.replace("h", "H")
|
|
68
|
+
.replace("cnot", "CNOT")
|
|
69
|
+
.replace("x", "X")
|
|
70
|
+
.replace("y", "Y")
|
|
71
|
+
.replace("z", "Z")
|
|
72
|
+
.replace("x90", "X90")
|
|
73
|
+
.replace("mx90", "mX90")
|
|
74
|
+
.replace("y90", "Y90")
|
|
75
|
+
.replace("my90", "mY90")
|
|
76
|
+
.replace("RZ", "Rz")
|
|
77
|
+
.replace("RX", "Rx")
|
|
78
|
+
.replace("cz", "CZ")
|
|
79
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Module containing the Quantum Algorithm class."""
|
|
2
|
+
|
|
3
|
+
from compute_api_client import AlgorithmType
|
|
4
|
+
|
|
5
|
+
from quantuminspire.sdk.models.file_algorithm import FileAlgorithm
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CqasmAlgorithm(FileAlgorithm):
|
|
9
|
+
"""A container object, reading the python algorithm and keeping metadata.
|
|
10
|
+
|
|
11
|
+
The CqasmAlgorithm reads the python file describing the algorithm and stores it in `.content`.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def content_type(self) -> AlgorithmType:
|
|
16
|
+
return AlgorithmType.QUANTUM
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def language_name(self) -> str:
|
|
20
|
+
return "cQASM"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Module containing the Hybrid Quantum/Classical Algorithm class."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from compute_api_client import CompileStage
|
|
6
|
+
|
|
7
|
+
from quantuminspire.sdk.models.base_algorithm import BaseAlgorithm
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FileAlgorithm(BaseAlgorithm):
|
|
11
|
+
"""A container object, reading the python algorithm and keeping metadata.
|
|
12
|
+
|
|
13
|
+
The FileAlgorithm reads the python file describing the algorithm and stores it in `.content`.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, platform_name: str, program_name: str) -> None:
|
|
17
|
+
super().__init__(platform_name, program_name)
|
|
18
|
+
self._content = ""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def content(self) -> str:
|
|
22
|
+
return self._content
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def compile_stage(self) -> CompileStage:
|
|
26
|
+
return CompileStage.NONE
|
|
27
|
+
|
|
28
|
+
def read_file(self, file_pointer: Path) -> None:
|
|
29
|
+
"""Read the python file to the wrapper.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
file_pointer: the file path to the file to read.
|
|
33
|
+
"""
|
|
34
|
+
self._content = file_pointer.read_text()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Module containing the Hybrid Quantum/Classical Algorithm class."""
|
|
2
|
+
|
|
3
|
+
from compute_api_client import AlgorithmType
|
|
4
|
+
|
|
5
|
+
from quantuminspire.sdk.models.file_algorithm import FileAlgorithm
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HybridAlgorithm(FileAlgorithm):
|
|
9
|
+
"""A container object, reading the python algorithm and keeping metadata.
|
|
10
|
+
|
|
11
|
+
The HybridAlgorithm reads the python file describing the algorithm and stores it in `.content`.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def content_type(self) -> AlgorithmType:
|
|
16
|
+
return AlgorithmType.HYBRID
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def language_name(self) -> str:
|
|
20
|
+
return "Python"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class JobOptions(BaseModel):
|
|
5
|
+
"""Options for running a job.
|
|
6
|
+
|
|
7
|
+
Subset of fields in the JobIn schema.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
number_of_shots: int = Field(
|
|
11
|
+
1024, ge=1, description="Number of shots for the job (only pure cQASM algorithms supported)."
|
|
12
|
+
)
|
|
13
|
+
raw_data_enabled: bool = Field(
|
|
14
|
+
False, description="Whether to enable shot memory for the job (only pure cQASM algorithms supported)."
|
|
15
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Quantum Inspire SDK.
|
|
2
|
+
|
|
3
|
+
Copyright 2022 QuTech Delft
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
|
6
|
+
License. You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
|
|
11
|
+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
language governing permissions and limitations under the License.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Any, List
|
|
16
|
+
|
|
17
|
+
from quantuminspire.util.api.quantum_interface import ExecuteCircuitResult, QuantumInterface as QuantumInterfaceProtocol
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
20
|
+
from quantuminspire.util.api.local_backend import LocalBackend
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class QuantumInterface(QuantumInterfaceProtocol):
|
|
24
|
+
"""Quantum Interface implementation for running quantum circuits from hybrid algorithms."""
|
|
25
|
+
|
|
26
|
+
# pylint: disable = R0903
|
|
27
|
+
# R0903: Too few public methods (1/2) (too-few-public-methods)
|
|
28
|
+
|
|
29
|
+
def __init__(self, backend: "LocalBackend"):
|
|
30
|
+
self.backend = backend
|
|
31
|
+
self.results: List[Any] = []
|
|
32
|
+
|
|
33
|
+
def execute_circuit(self, circuit: str, number_of_shots: int) -> ExecuteCircuitResult:
|
|
34
|
+
"""Execute a quantum circuit."""
|
|
35
|
+
results = self.backend.run_quantum(circuit, number_of_shots)
|
|
36
|
+
self.results.append(results.results)
|
|
37
|
+
return results
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Quantum Inspire SDK.
|
|
2
|
+
|
|
3
|
+
Copyright 2022 QuTech Delft
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
|
6
|
+
License. You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
|
|
11
|
+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
language governing permissions and limitations under the License.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from quantuminspire.sdk.models.base_algorithm import BaseAlgorithm
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseBackend(ABC):
|
|
22
|
+
"""Base interface for backends.
|
|
23
|
+
|
|
24
|
+
A backend will execute the hybrid algorithm, or quantum circuit provided.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def run(self, program: BaseAlgorithm, backend_type_id: int) -> int:
|
|
29
|
+
"""Execute provided algorithm/circuit."""
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def get_results(self, job_id: int) -> Any:
|
|
34
|
+
"""Get results for algorithm/circuit."""
|
|
35
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Quantum Inspire SDK.
|
|
2
|
+
|
|
3
|
+
Copyright 2022 QuTech Delft
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
|
6
|
+
License. You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
|
|
11
|
+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
language governing permissions and limitations under the License.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import importlib
|
|
16
|
+
import sys
|
|
17
|
+
import types
|
|
18
|
+
from enum import IntEnum
|
|
19
|
+
from typing import Any, Union
|
|
20
|
+
|
|
21
|
+
from quantuminspire.sdk.models.base_algorithm import BaseAlgorithm
|
|
22
|
+
from quantuminspire.sdk.models.circuit import Circuit
|
|
23
|
+
from quantuminspire.sdk.models.hybrid_algorithm import HybridAlgorithm
|
|
24
|
+
from quantuminspire.sdk.quantum_interface import QuantumInterface
|
|
25
|
+
from quantuminspire.util.api.base_backend import BaseBackend
|
|
26
|
+
from quantuminspire.util.api.quantum_interface import ExecuteCircuitResult, QuantumInterface as QuantumInterfaceProtocol
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def import_qxelarator() -> types.ModuleType: # pragma: no cover
|
|
30
|
+
"""Helper function for importing optional dependency qxelarator."""
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
return importlib.import_module("qxelarator")
|
|
34
|
+
except ModuleNotFoundError as exc:
|
|
35
|
+
print(
|
|
36
|
+
"Error: Dependencies for local execution not installed, install with 'pip install quantuminspire[local]'",
|
|
37
|
+
file=sys.stderr,
|
|
38
|
+
)
|
|
39
|
+
raise SystemExit from exc
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class LocalBackend(BaseBackend):
|
|
43
|
+
"""Connection to remote backend.
|
|
44
|
+
|
|
45
|
+
Create a connection with the remote backend for Quantum Inspire. This connection creates the appropriate projects,
|
|
46
|
+
algorithms, files etc. The algorithm/circuit will also be run.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
class JobFakeID(IntEnum):
|
|
50
|
+
"""Helper to conform to BaseBackend interface of returning job IDs."""
|
|
51
|
+
|
|
52
|
+
QUANTUM = -1
|
|
53
|
+
HYBRID = -2
|
|
54
|
+
|
|
55
|
+
def __init__(self, qxelarator: Union[types.ModuleType, None] = None) -> None:
|
|
56
|
+
super().__init__()
|
|
57
|
+
self._quantum_results: Union[ExecuteCircuitResult, None] = None
|
|
58
|
+
self._hybrid_results: Union[Any, None] = None
|
|
59
|
+
self._qxelarator = qxelarator if qxelarator else import_qxelarator()
|
|
60
|
+
|
|
61
|
+
def run(self, program: BaseAlgorithm, backend_type_id: int) -> int:
|
|
62
|
+
"""Execute provided algorithm/circuit."""
|
|
63
|
+
if isinstance(program, HybridAlgorithm):
|
|
64
|
+
quantum_interface = QuantumInterface(self)
|
|
65
|
+
self._hybrid_results = self.run_hybrid(program, quantum_interface)
|
|
66
|
+
return self.JobFakeID.HYBRID
|
|
67
|
+
if isinstance(program, Circuit):
|
|
68
|
+
self._quantum_results = self.run_quantum(program.content)
|
|
69
|
+
return self.JobFakeID.QUANTUM
|
|
70
|
+
raise AssertionError("Unknown algorithm type")
|
|
71
|
+
|
|
72
|
+
def run_hybrid(self, algorithm: HybridAlgorithm, quantum_interface: QuantumInterfaceProtocol) -> Any:
|
|
73
|
+
"""Execute provided algorithm."""
|
|
74
|
+
|
|
75
|
+
# pylint: disable = E1101, W0122
|
|
76
|
+
# E1101: Instance of 'module' has no 'execute' member (no-member)
|
|
77
|
+
# W0122: Use of exec (exec-used)
|
|
78
|
+
|
|
79
|
+
program = types.ModuleType("mod")
|
|
80
|
+
|
|
81
|
+
exec(algorithm.content, program.__dict__)
|
|
82
|
+
|
|
83
|
+
if not hasattr(program, "execute") or not callable(program.execute):
|
|
84
|
+
raise AssertionError("'execute' hook not found or not callable.")
|
|
85
|
+
program.execute(quantum_interface)
|
|
86
|
+
|
|
87
|
+
if not hasattr(program, "finalize") or not callable(program.finalize):
|
|
88
|
+
raise AssertionError("'finalize' hook not found or not callable.")
|
|
89
|
+
result = program.finalize(quantum_interface.results)
|
|
90
|
+
if not isinstance(result, dict):
|
|
91
|
+
raise AssertionError("'finalize' hook returned invalid type, must be dict.")
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
def run_quantum(self, circuit: str, number_of_shots: int = 1) -> ExecuteCircuitResult:
|
|
95
|
+
"""Execute provided circuit."""
|
|
96
|
+
result = self._qxelarator.execute_string(circuit, iterations=number_of_shots)
|
|
97
|
+
return ExecuteCircuitResult(
|
|
98
|
+
results=result.results,
|
|
99
|
+
shots_done=result.shots_done,
|
|
100
|
+
shots_requested=result.shots_requested,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def get_results(self, job_id: int) -> Any:
|
|
104
|
+
"""Get results for algorithm/circuit."""
|
|
105
|
+
if job_id == self.JobFakeID.QUANTUM:
|
|
106
|
+
return self._quantum_results
|
|
107
|
+
if job_id == self.JobFakeID.HYBRID:
|
|
108
|
+
return self._hybrid_results
|
|
109
|
+
|
|
110
|
+
raise AssertionError("Unknown job id")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Quantum Inspire SDK.
|
|
2
|
+
|
|
3
|
+
Copyright 2022 QuTech Delft
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
|
6
|
+
License. You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
|
|
11
|
+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
|
12
|
+
language governing permissions and limitations under the License.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
from typing import Any, Dict, List
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ExecuteCircuitResult(BaseModel):
|
|
22
|
+
"""Result of executing a quantum circuit."""
|
|
23
|
+
|
|
24
|
+
results: Dict[str, float]
|
|
25
|
+
shots_requested: int
|
|
26
|
+
shots_done: int
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class QuantumInterface(ABC):
|
|
30
|
+
"""Interface for running quantum circuits from hybrid algorithms."""
|
|
31
|
+
|
|
32
|
+
# pylint: disable = R0903
|
|
33
|
+
# Too few public methods (1/2) (too-few-public-methods)
|
|
34
|
+
|
|
35
|
+
results: List[Any]
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def execute_circuit(self, circuit: str, number_of_shots: int) -> ExecuteCircuitResult:
|
|
39
|
+
"""Execute a quantum circuit."""
|
|
40
|
+
raise NotImplementedError
|