classiq 0.104.0__py3-none-any.whl → 1.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.
- classiq/__init__.py +2 -0
- classiq/_internals/authentication/auth0.py +29 -0
- classiq/_internals/authentication/auth_flow_factory.py +43 -0
- classiq/_internals/authentication/machine_credentials_flow.py +26 -0
- classiq/_internals/authentication/password_manager.py +84 -0
- classiq/_internals/authentication/token_manager.py +24 -8
- classiq/analyzer/show_interactive_hack.py +0 -8
- classiq/applications/combinatorial_optimization/combinatorial_problem.py +1 -1
- classiq/execution/all_hardware_devices.py +59 -1
- classiq/execution/functions/__init__.py +11 -1
- classiq/execution/functions/expectation_value.py +106 -0
- classiq/execution/functions/minimize.py +90 -0
- classiq/execution/functions/sample.py +8 -189
- classiq/execution/functions/state_vector.py +113 -0
- classiq/execution/functions/util/__init__.py +0 -0
- classiq/execution/functions/util/backend_preferences.py +188 -0
- classiq/interface/_version.py +1 -1
- classiq/interface/backend/backend_preferences.py +66 -0
- classiq/interface/backend/quantum_backend_providers.py +11 -0
- classiq/interface/exceptions.py +0 -4
- classiq/interface/generator/arith/binary_ops.py +24 -0
- classiq/interface/generator/arith/number_utils.py +15 -6
- classiq/interface/generator/compiler_keywords.py +1 -0
- classiq/interface/generator/function_param_list.py +4 -0
- classiq/interface/generator/function_params.py +1 -1
- classiq/interface/generator/functions/classical_type.py +15 -0
- classiq/interface/generator/functions/type_name.py +17 -4
- classiq/interface/generator/transpiler_basis_gates.py +1 -0
- classiq/interface/generator/types/compilation_metadata.py +15 -6
- classiq/interface/hardware.py +1 -0
- classiq/interface/interface_version.py +1 -1
- classiq/interface/model/model.py +19 -0
- classiq/interface/model/quantum_type.py +15 -0
- classiq/interface/qubits_mapping/__init__.py +4 -0
- classiq/interface/qubits_mapping/path_expr_range.py +69 -0
- classiq/interface/qubits_mapping/qubits_mapping.py +231 -0
- classiq/interface/qubits_mapping/slices.py +112 -0
- classiq/model_expansions/arithmetic.py +6 -0
- classiq/qmod/builtins/functions/__init__.py +12 -9
- classiq/qmod/builtins/functions/allocation.py +0 -36
- classiq/qmod/builtins/functions/arithmetic.py +52 -0
- classiq/qmod/builtins/functions/gray_code.py +23 -0
- classiq/qmod/builtins/functions/mcx_func.py +10 -0
- classiq/qmod/builtins/structs.py +22 -3
- classiq/qprog_to_cudaq.py +347 -0
- {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/METADATA +4 -1
- {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/RECORD +52 -39
- /classiq/execution/functions/{_logging.py → util/_logging.py} +0 -0
- /classiq/execution/functions/{constants.py → util/constants.py} +0 -0
- /classiq/execution/functions/{parse_provider_backend.py → util/parse_provider_backend.py} +0 -0
- {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/WHEEL +0 -0
- {classiq-0.104.0.dist-info → classiq-1.0.0.dist-info}/licenses/LICENSE.txt +0 -0
classiq/__init__.py
CHANGED
|
@@ -52,6 +52,7 @@ from classiq.open_library import * # noqa: F403
|
|
|
52
52
|
from classiq.open_library import __all__ as _open_library_all
|
|
53
53
|
from classiq.qmod import * # noqa: F403
|
|
54
54
|
from classiq.qmod import __all__ as _qmod_all
|
|
55
|
+
from classiq.qprog_to_cudaq import qprog_to_cudaq_kernel
|
|
55
56
|
from classiq.quantum_program import ExecutionParams, assign_parameters, transpile
|
|
56
57
|
from classiq.synthesis import (
|
|
57
58
|
QmodFormat,
|
|
@@ -117,6 +118,7 @@ __all__ = (
|
|
|
117
118
|
"matrix_to_hamiltonian",
|
|
118
119
|
"matrix_to_pauli_operator",
|
|
119
120
|
"pauli_operator_to_matrix",
|
|
121
|
+
"qprog_to_cudaq_kernel",
|
|
120
122
|
"quantum_program_from_qasm",
|
|
121
123
|
"quantum_program_from_qasm_async",
|
|
122
124
|
"QmodFormat",
|
|
@@ -11,6 +11,8 @@ from classiq.interface.exceptions import ClassiqAuthenticationError
|
|
|
11
11
|
|
|
12
12
|
from classiq._internals.config import CONFIG_ENV_FILES
|
|
13
13
|
|
|
14
|
+
GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
class AuthSettings(BaseSettings):
|
|
16
18
|
domain: str = Field(
|
|
@@ -144,3 +146,30 @@ class Auth0:
|
|
|
144
146
|
access_token=data["access_token"],
|
|
145
147
|
refresh_token=data.get("refresh_token", None),
|
|
146
148
|
)
|
|
149
|
+
|
|
150
|
+
async def client_credentials_login(
|
|
151
|
+
self, client_id: str, client_secret: str, organization: str | None = None
|
|
152
|
+
) -> dict[str, Any]:
|
|
153
|
+
"""Authenticate using client credentials (M2M flow).
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
client_id: The M2M application client ID
|
|
157
|
+
client_secret: The M2M application client secret
|
|
158
|
+
organization: Optional organization ID for organization-specific access
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Dictionary containing token data from Auth0
|
|
162
|
+
"""
|
|
163
|
+
payload = {
|
|
164
|
+
"client_id": client_id,
|
|
165
|
+
"client_secret": client_secret,
|
|
166
|
+
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
|
|
167
|
+
"audience": self._auth_settings.audience,
|
|
168
|
+
}
|
|
169
|
+
if organization:
|
|
170
|
+
payload["organization"] = organization
|
|
171
|
+
|
|
172
|
+
return await self._make_request(
|
|
173
|
+
url="/oauth/token",
|
|
174
|
+
payload=payload,
|
|
175
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from classiq._internals.authentication import password_manager as pm
|
|
4
|
+
from classiq._internals.authentication.authorization_flow import AuthorizationFlow
|
|
5
|
+
from classiq._internals.authentication.hybrid_flow import HybridFlow
|
|
6
|
+
from classiq._internals.authentication.machine_credentials_flow import (
|
|
7
|
+
M2MClientCredentialsFlow,
|
|
8
|
+
)
|
|
9
|
+
from classiq._internals.config import Configuration
|
|
10
|
+
|
|
11
|
+
_logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthFlowFactory:
|
|
15
|
+
"""Factory for creating appropriate authorization and authentication flows based on context."""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def create_flow(
|
|
19
|
+
password_manager: pm.PasswordManager, config: Configuration
|
|
20
|
+
) -> AuthorizationFlow:
|
|
21
|
+
"""
|
|
22
|
+
Create an authentication with or without authentication flow based on the password manager type.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
password_manager: The password manager instance.
|
|
26
|
+
config: Configuration for authentication.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Appropriate AuthorizationFlow instance (M2MClientCredentialsFlow or HybridFlow).
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ClassiqAuthenticationError: If M2M token generation failed.
|
|
33
|
+
"""
|
|
34
|
+
# M2M authentication flow using client credentials to authenticate with Auth0 and get access token
|
|
35
|
+
if isinstance(password_manager, pm.M2MPasswordManager):
|
|
36
|
+
return M2MClientCredentialsFlow(
|
|
37
|
+
client_id=password_manager.client_id,
|
|
38
|
+
client_secret=password_manager.client_secret,
|
|
39
|
+
organization=password_manager.org_id,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Interactive user authentication flow
|
|
43
|
+
return HybridFlow(require_refresh_token=True, text_only=config.text_only)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from classiq._internals.authentication.auth0 import Tokens
|
|
2
|
+
from classiq._internals.authentication.authorization_flow import AuthorizationFlow
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class M2MClientCredentialsFlow(AuthorizationFlow):
|
|
6
|
+
"""Machine-to-Machine (M2M) authentication using OAuth2 Client Credentials flow."""
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
client_id: str,
|
|
11
|
+
client_secret: str,
|
|
12
|
+
organization: str | None = None,
|
|
13
|
+
) -> None:
|
|
14
|
+
# M2M authentication doesn't use refresh tokens or interactive flows
|
|
15
|
+
super().__init__(require_refresh_token=False, text_only=False)
|
|
16
|
+
self.client_id = client_id
|
|
17
|
+
self.client_secret = client_secret
|
|
18
|
+
self.organization = organization
|
|
19
|
+
|
|
20
|
+
async def get_tokens(self) -> Tokens:
|
|
21
|
+
data = await self.auth0_client.client_credentials_login(
|
|
22
|
+
client_id=self.client_id,
|
|
23
|
+
client_secret=self.client_secret,
|
|
24
|
+
organization=self.organization,
|
|
25
|
+
)
|
|
26
|
+
return self.handle_ready_data(data)
|
|
@@ -37,6 +37,8 @@ class PasswordManagerSettings(BaseSettings):
|
|
|
37
37
|
|
|
38
38
|
class PasswordManager(abc.ABC):
|
|
39
39
|
_SERVICE_NAME: str = "classiqTokenService"
|
|
40
|
+
text_only_supported: bool = True
|
|
41
|
+
has_refresh_token: bool = True
|
|
40
42
|
|
|
41
43
|
def __init__(self) -> None:
|
|
42
44
|
self._settings = PasswordManagerSettings()
|
|
@@ -76,6 +78,8 @@ class PasswordManager(abc.ABC):
|
|
|
76
78
|
|
|
77
79
|
|
|
78
80
|
class KeyringPasswordManager(PasswordManager):
|
|
81
|
+
text_only_supported: bool = False
|
|
82
|
+
|
|
79
83
|
def _get(self, key: str) -> str | None:
|
|
80
84
|
return keyring.get_password(service_name=self._SERVICE_NAME, username=key)
|
|
81
85
|
|
|
@@ -154,3 +158,83 @@ class FilePasswordManager(PasswordManager):
|
|
|
154
158
|
@staticmethod
|
|
155
159
|
def is_supported() -> bool:
|
|
156
160
|
return "windows" not in platform.platform().lower()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class M2MPasswordManager(PasswordManager):
|
|
164
|
+
"""Password manager for M2M authentication using environment variables.
|
|
165
|
+
|
|
166
|
+
Supports machine-to-machine authentication by reading credentials from:
|
|
167
|
+
- CLASSIQ_CLIENT_ID (required)
|
|
168
|
+
- CLASSIQ_CLIENT_SECRET (required)
|
|
169
|
+
- CLASSIQ_ORG_ID (optional)
|
|
170
|
+
|
|
171
|
+
This manager stores tokens in memory after M2M authentication.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
has_refresh_token: bool = False
|
|
175
|
+
|
|
176
|
+
M2M_CREDS_ENV_KEYS = {
|
|
177
|
+
"client_id": "CLASSIQ_CLIENT_ID",
|
|
178
|
+
"client_secret": "CLASSIQ_CLIENT_SECRET",
|
|
179
|
+
"org_id": "CLASSIQ_ORG_ID",
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
M2M_REQUIRED_CREDS = {"client_id", "client_secret"}
|
|
183
|
+
|
|
184
|
+
def __init__(self) -> None:
|
|
185
|
+
super().__init__()
|
|
186
|
+
self._token_storage: dict[str, str | None] = {}
|
|
187
|
+
# Initialize all environment variable values
|
|
188
|
+
self._m2m_creds: dict[str, str | None] = {
|
|
189
|
+
key: os.getenv(env_key) for key, env_key in self.M2M_CREDS_ENV_KEYS.items()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
def _get(self, key: str) -> str | None:
|
|
193
|
+
"""Get token from in-memory storage."""
|
|
194
|
+
return self._token_storage.get(key)
|
|
195
|
+
|
|
196
|
+
def _set(self, key: str, value: str | None) -> None:
|
|
197
|
+
"""Store token in memory."""
|
|
198
|
+
if value is None:
|
|
199
|
+
self._clear(key)
|
|
200
|
+
return
|
|
201
|
+
self._token_storage[key] = value
|
|
202
|
+
|
|
203
|
+
def _clear(self, key: str) -> None:
|
|
204
|
+
"""Clear token from memory storage."""
|
|
205
|
+
self._token_storage.pop(key, None)
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def is_supported() -> bool:
|
|
209
|
+
"""Check if required M2M environment variables are present."""
|
|
210
|
+
manager = M2MPasswordManager()
|
|
211
|
+
return all(
|
|
212
|
+
manager._m2m_creds.get(key) for key in M2MPasswordManager.M2M_REQUIRED_CREDS
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def _get_required_cred(self, key: str) -> str:
|
|
216
|
+
value = self._m2m_creds.get(key)
|
|
217
|
+
if value is None:
|
|
218
|
+
raise ValueError(
|
|
219
|
+
f"M2M authentication requires {self.M2M_CREDS_ENV_KEYS[key]} environment variable"
|
|
220
|
+
)
|
|
221
|
+
return value
|
|
222
|
+
|
|
223
|
+
# to prevent mypy from complaining about the return type
|
|
224
|
+
def _get_optional_cred(self, key: str) -> str | None:
|
|
225
|
+
return self._m2m_creds.get(key)
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def client_id(self) -> str:
|
|
229
|
+
"""Get client ID from initialized credentials."""
|
|
230
|
+
return self._get_required_cred("client_id")
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def client_secret(self) -> str:
|
|
234
|
+
"""Get client secret from initialized credentials."""
|
|
235
|
+
return self._get_required_cred("client_secret")
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def org_id(self) -> str | None:
|
|
239
|
+
"""Get organization ID from initialized credentials (optional)."""
|
|
240
|
+
return self._get_optional_cred("org_id")
|
|
@@ -11,13 +11,15 @@ from classiq.interface.exceptions import (
|
|
|
11
11
|
|
|
12
12
|
from classiq._internals.authentication import password_manager as pm
|
|
13
13
|
from classiq._internals.authentication.auth0 import Auth0, Tokens
|
|
14
|
-
from classiq._internals.authentication.
|
|
14
|
+
from classiq._internals.authentication.auth_flow_factory import AuthFlowFactory
|
|
15
15
|
from classiq._internals.config import Configuration
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
SUPPORTED_PASSWORD_MANAGERS: Sequence[type[pm.PasswordManager]] = [
|
|
18
|
+
pm.M2MPasswordManager,
|
|
18
19
|
pm.KeyringPasswordManager,
|
|
19
20
|
pm.FilePasswordManager,
|
|
20
21
|
]
|
|
22
|
+
|
|
21
23
|
_logger = logging.getLogger(__name__)
|
|
22
24
|
|
|
23
25
|
|
|
@@ -57,9 +59,11 @@ class TokenManager:
|
|
|
57
59
|
|
|
58
60
|
if should_skip_authentication:
|
|
59
61
|
return pm.DummyPasswordManager()
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
for password_manager in
|
|
62
|
+
|
|
63
|
+
# Filter out M2MPasswordManager and FilePasswordManager in text-only mode
|
|
64
|
+
for password_manager in SUPPORTED_PASSWORD_MANAGERS:
|
|
65
|
+
if config.text_only and not password_manager.text_only_supported:
|
|
66
|
+
continue
|
|
63
67
|
if password_manager.is_supported():
|
|
64
68
|
return password_manager()
|
|
65
69
|
raise ClassiqPasswordManagerSelectionError(
|
|
@@ -75,6 +79,10 @@ class TokenManager:
|
|
|
75
79
|
self._save_tokens(tokens)
|
|
76
80
|
|
|
77
81
|
async def update_expired_access_token(self) -> None:
|
|
82
|
+
if not self._password_manager.has_refresh_token:
|
|
83
|
+
await self._authentication_helper()
|
|
84
|
+
return
|
|
85
|
+
|
|
78
86
|
if self._refresh_token is not None:
|
|
79
87
|
await self._refresh()
|
|
80
88
|
return
|
|
@@ -84,6 +92,11 @@ class TokenManager:
|
|
|
84
92
|
)
|
|
85
93
|
|
|
86
94
|
async def manual_authentication(self, overwrite: bool) -> None:
|
|
95
|
+
# For M2M authentication, always re-authenticate (no refresh token concept)
|
|
96
|
+
if not self._password_manager.has_refresh_token:
|
|
97
|
+
await self._authentication_helper()
|
|
98
|
+
return
|
|
99
|
+
|
|
87
100
|
if self._refresh_token is None:
|
|
88
101
|
await self._authentication_helper()
|
|
89
102
|
return
|
|
@@ -126,8 +139,11 @@ class TokenManager:
|
|
|
126
139
|
async def _authentication_helper(self) -> None:
|
|
127
140
|
# TODO: consider using refresh token rotation
|
|
128
141
|
# (https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation)
|
|
129
|
-
authorization_flow =
|
|
130
|
-
|
|
142
|
+
authorization_flow = AuthFlowFactory.create_flow(
|
|
143
|
+
password_manager=self._password_manager, config=self._config
|
|
131
144
|
)
|
|
132
145
|
tokens = await authorization_flow.get_tokens()
|
|
133
|
-
|
|
146
|
+
|
|
147
|
+
# Password managers without refresh tokens don't use rotation
|
|
148
|
+
force_override = self._password_manager.has_refresh_token
|
|
149
|
+
self._save_tokens(tokens, force_override_refresh_token=force_override)
|
|
@@ -7,8 +7,6 @@ from tempfile import NamedTemporaryFile
|
|
|
7
7
|
from urllib.parse import urljoin
|
|
8
8
|
|
|
9
9
|
from classiq.interface.analyzer.result import DataID
|
|
10
|
-
from classiq.interface.exceptions import ClassiqAnalyzerVisualizationError
|
|
11
|
-
from classiq.interface.generator.model.preferences.preferences import QuantumFormat
|
|
12
10
|
from classiq.interface.generator.quantum_program import QuantumProgram
|
|
13
11
|
|
|
14
12
|
from classiq._internals.api_wrapper import ApiWrapper
|
|
@@ -116,12 +114,6 @@ def get_visualization_renderer() -> VisualizationRenderer:
|
|
|
116
114
|
|
|
117
115
|
|
|
118
116
|
async def handle_remote_app(circuit: QuantumProgram, display_url: bool = True) -> None:
|
|
119
|
-
if circuit.outputs.get(QuantumFormat.QASM) is None:
|
|
120
|
-
raise ClassiqAnalyzerVisualizationError(
|
|
121
|
-
"Missing QASM transpilation: visualization is only supported "
|
|
122
|
-
"for QASM programs. Try adding QASM to the output formats "
|
|
123
|
-
"synthesis preferences"
|
|
124
|
-
)
|
|
125
117
|
circuit_dataid = DataID(id=circuit.program_id)
|
|
126
118
|
|
|
127
119
|
renderer = get_visualization_renderer()
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from classiq.interface.hardware import HardwareInformation, Provider
|
|
2
4
|
|
|
3
5
|
from classiq._internals import async_utils
|
|
4
6
|
from classiq._internals.api_wrapper import ApiWrapper
|
|
7
|
+
from classiq.execution.functions.util.parse_provider_backend import (
|
|
8
|
+
_PROVIDER_TO_CANONICAL_NAME,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from pandas import DataFrame
|
|
5
13
|
|
|
6
14
|
|
|
7
15
|
def get_all_hardware_devices() -> list[HardwareInformation]:
|
|
@@ -9,3 +17,53 @@ def get_all_hardware_devices() -> list[HardwareInformation]:
|
|
|
9
17
|
Returns a list of all hardware devices known to Classiq.
|
|
10
18
|
"""
|
|
11
19
|
return async_utils.run(ApiWrapper.call_get_all_hardware_devices())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _extract_relevant_hardware_fields_for_user(info: HardwareInformation) -> tuple:
|
|
23
|
+
return (
|
|
24
|
+
_PROVIDER_TO_CANONICAL_NAME[info.provider],
|
|
25
|
+
info.name,
|
|
26
|
+
info.number_of_qubits,
|
|
27
|
+
info.status.availability.is_available,
|
|
28
|
+
info.status.pending_jobs,
|
|
29
|
+
info.status.queue_time,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_NON_DISPLAYED_DEVICES = [
|
|
34
|
+
(Provider.CLASSIQ, "simulator_statevector"),
|
|
35
|
+
(Provider.CLASSIQ, "nvidia_simulator_statevector"),
|
|
36
|
+
(Provider.GOOGLE, "cuquantum_statevector"),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_backend_details() -> "DataFrame":
|
|
41
|
+
"""
|
|
42
|
+
Returns a pandas DataFrame containing hardware devices known to Classiq.
|
|
43
|
+
"""
|
|
44
|
+
from pandas import DataFrame
|
|
45
|
+
|
|
46
|
+
devices = get_all_hardware_devices()
|
|
47
|
+
displayed_devices = [
|
|
48
|
+
device
|
|
49
|
+
for device in devices
|
|
50
|
+
if (device.provider, device.name) not in _NON_DISPLAYED_DEVICES
|
|
51
|
+
]
|
|
52
|
+
# Remove "ibm_" prefix.
|
|
53
|
+
for device in displayed_devices:
|
|
54
|
+
if device.provider == Provider.IBM_QUANTUM and device.name.startswith("ibm_"):
|
|
55
|
+
device.name = device.name[len("ibm_") :]
|
|
56
|
+
tuples = [
|
|
57
|
+
_extract_relevant_hardware_fields_for_user(info) for info in displayed_devices
|
|
58
|
+
]
|
|
59
|
+
return DataFrame(
|
|
60
|
+
tuples,
|
|
61
|
+
columns=[
|
|
62
|
+
"provider",
|
|
63
|
+
"name",
|
|
64
|
+
"number of qubits",
|
|
65
|
+
"available",
|
|
66
|
+
"pending jobs",
|
|
67
|
+
"queue time",
|
|
68
|
+
],
|
|
69
|
+
)
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
from classiq.execution.functions.expectation_value import _get_expectation_value
|
|
2
|
+
from classiq.execution.functions.minimize import _minimize
|
|
1
3
|
from classiq.execution.functions.sample import _new_sample
|
|
4
|
+
from classiq.execution.functions.state_vector import _calculate_state_vector
|
|
5
|
+
from classiq.execution.functions.util.constants import Verbosity
|
|
2
6
|
|
|
3
|
-
__all__ = [
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Verbosity",
|
|
9
|
+
"_calculate_state_vector",
|
|
10
|
+
"_get_expectation_value",
|
|
11
|
+
"_minimize",
|
|
12
|
+
"_new_sample",
|
|
13
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from classiq.interface.backend.backend_preferences import (
|
|
4
|
+
BackendPreferencesTypes,
|
|
5
|
+
ClassiqBackendPreferences,
|
|
6
|
+
)
|
|
7
|
+
from classiq.interface.backend.provider_config.provider_config import ProviderConfig
|
|
8
|
+
from classiq.interface.backend.quantum_backend_providers import (
|
|
9
|
+
ClassiqSimulatorBackendNames,
|
|
10
|
+
)
|
|
11
|
+
from classiq.interface.executor.execution_preferences import ExecutionPreferences
|
|
12
|
+
from classiq.interface.generator.model.preferences import create_random_seed
|
|
13
|
+
from classiq.interface.generator.model.preferences.preferences import (
|
|
14
|
+
TranspilationOption,
|
|
15
|
+
)
|
|
16
|
+
from classiq.interface.hardware import Provider
|
|
17
|
+
|
|
18
|
+
from classiq import (
|
|
19
|
+
ExecutionParams,
|
|
20
|
+
QuantumProgram,
|
|
21
|
+
)
|
|
22
|
+
from classiq.execution.execution_session import ExecutionSession
|
|
23
|
+
from classiq.execution.functions.util._logging import _logger
|
|
24
|
+
from classiq.execution.functions.util.backend_preferences import (
|
|
25
|
+
_get_backend_preferences_from_specifier,
|
|
26
|
+
)
|
|
27
|
+
from classiq.execution.functions.util.constants import Verbosity
|
|
28
|
+
from classiq.execution.functions.util.parse_provider_backend import (
|
|
29
|
+
_parse_provider_backend,
|
|
30
|
+
)
|
|
31
|
+
from classiq.qmod.builtins.structs import SparsePauliOp
|
|
32
|
+
|
|
33
|
+
_DEFAULT_BACKEND_NAME = "simulator"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_backend_preferences(
|
|
37
|
+
backend: str, estimate: bool, config: dict[str, Any] | ProviderConfig | None
|
|
38
|
+
) -> BackendPreferencesTypes:
|
|
39
|
+
provider, backend_name = _parse_provider_backend(backend)
|
|
40
|
+
backend_preferences: BackendPreferencesTypes
|
|
41
|
+
if not estimate:
|
|
42
|
+
if not (
|
|
43
|
+
provider == Provider.CLASSIQ and backend_name.lower().strip() == "simulator"
|
|
44
|
+
):
|
|
45
|
+
raise ValueError(
|
|
46
|
+
"Calculating exact expectation value is supported only for the 'classiq/simulator' backend"
|
|
47
|
+
)
|
|
48
|
+
backend_preferences = ClassiqBackendPreferences(
|
|
49
|
+
# This backend name is for exact simulation
|
|
50
|
+
backend_name=ClassiqSimulatorBackendNames.SIMULATOR_STATEVECTOR
|
|
51
|
+
)
|
|
52
|
+
else:
|
|
53
|
+
backend_preferences = _get_backend_preferences_from_specifier(
|
|
54
|
+
backend, config or {}
|
|
55
|
+
)
|
|
56
|
+
return backend_preferences
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _get_expectation_value(
|
|
60
|
+
qprog: QuantumProgram,
|
|
61
|
+
observable: SparsePauliOp,
|
|
62
|
+
backend: str | None = None,
|
|
63
|
+
*,
|
|
64
|
+
estimate: bool = True,
|
|
65
|
+
parameters: ExecutionParams | None = None,
|
|
66
|
+
config: dict[str, Any] | ProviderConfig | None = None,
|
|
67
|
+
num_shots: int | None = None,
|
|
68
|
+
random_seed: int | None = None,
|
|
69
|
+
transpilation_option: TranspilationOption = TranspilationOption.DECOMPOSE,
|
|
70
|
+
verbosity: Verbosity = Verbosity.INFO,
|
|
71
|
+
) -> complex:
|
|
72
|
+
"""
|
|
73
|
+
Get the expectation value of the observable O with respect to the state |psi>
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
qprog: The quantum program which generates the state |psi> to be observed
|
|
77
|
+
observable: The observable O
|
|
78
|
+
backend: The device (hardware or simulator) on which to run the quantum program. Specified as "provider/device_id". Use the `get_backend_details` function to see supported devices.
|
|
79
|
+
estimate: Whether to estimate the expectation value by repeatedly measuring the circuit, or else calculate the expectation value using a simulator.
|
|
80
|
+
parameters: The classical parameters for the quantum program
|
|
81
|
+
config: Provider-specific configuration, such as api keys
|
|
82
|
+
num_shots:
|
|
83
|
+
random_seed: The random seed used for transpilation and simulation
|
|
84
|
+
transpilation_option: Advanced configuration for hardware-specific transpilation
|
|
85
|
+
verbosity: What level of information should be logged
|
|
86
|
+
|
|
87
|
+
Returns: The expectation value
|
|
88
|
+
"""
|
|
89
|
+
if backend is None:
|
|
90
|
+
backend = _DEFAULT_BACKEND_NAME
|
|
91
|
+
backend_preferences = _get_backend_preferences(backend, estimate, config)
|
|
92
|
+
ep = ExecutionPreferences(
|
|
93
|
+
backend_preferences=backend_preferences,
|
|
94
|
+
num_shots=num_shots,
|
|
95
|
+
random_seed=create_random_seed() if random_seed is None else random_seed,
|
|
96
|
+
transpile_to_hardware=transpilation_option,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if verbosity != Verbosity.QUIET:
|
|
100
|
+
_logger.info(f"Submitting job to {backend}")
|
|
101
|
+
with ExecutionSession(qprog, execution_preferences=ep) as session:
|
|
102
|
+
job = session.submit_estimate(hamiltonian=observable, parameters=parameters)
|
|
103
|
+
if verbosity != Verbosity.QUIET:
|
|
104
|
+
_logger.info(f"Job id: {job.id}")
|
|
105
|
+
result = job.get_estimate_result()
|
|
106
|
+
return result.value
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from classiq.interface.backend.provider_config.provider_config import ProviderConfig
|
|
4
|
+
from classiq.interface.executor.execution_preferences import ExecutionPreferences
|
|
5
|
+
from classiq.interface.generator.model.preferences import create_random_seed
|
|
6
|
+
from classiq.interface.generator.model.preferences.preferences import (
|
|
7
|
+
TranspilationOption,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from classiq import (
|
|
11
|
+
ExecutionParams,
|
|
12
|
+
QuantumProgram,
|
|
13
|
+
)
|
|
14
|
+
from classiq.execution.execution_session import ExecutionSession
|
|
15
|
+
from classiq.execution.functions.util._logging import _logger
|
|
16
|
+
from classiq.execution.functions.util.backend_preferences import (
|
|
17
|
+
_get_backend_preferences_from_specifier,
|
|
18
|
+
)
|
|
19
|
+
from classiq.execution.functions.util.constants import Verbosity
|
|
20
|
+
from classiq.qmod.builtins.structs import SparsePauliOp
|
|
21
|
+
from classiq.qmod.qmod_variable import QmodExpressionCreator
|
|
22
|
+
|
|
23
|
+
_DEFAULT_BACKEND_NAME = "simulator"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _minimize(
|
|
27
|
+
qprog: QuantumProgram,
|
|
28
|
+
cost_function: SparsePauliOp | QmodExpressionCreator,
|
|
29
|
+
initial_params: ExecutionParams,
|
|
30
|
+
max_iteration: int,
|
|
31
|
+
backend: str | None = None,
|
|
32
|
+
*,
|
|
33
|
+
quantile: float = 1.0,
|
|
34
|
+
tolerance: float | None = None,
|
|
35
|
+
config: dict[str, Any] | ProviderConfig | None = None,
|
|
36
|
+
random_seed: int | None = None,
|
|
37
|
+
transpilation_option: TranspilationOption = TranspilationOption.DECOMPOSE,
|
|
38
|
+
verbosity: Verbosity = Verbosity.INFO,
|
|
39
|
+
) -> list[tuple[float, ExecutionParams]]:
|
|
40
|
+
"""
|
|
41
|
+
Minimize the given cost function using the quantum program.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
qprog: The parametric quantum program that generates the state (ansatz).
|
|
45
|
+
Only quantum programs with exactly one execution parameter are supported.
|
|
46
|
+
cost_function: The cost function to minimize. It can be one of the following:
|
|
47
|
+
- A quantum cost function defined by a Hamiltonian.
|
|
48
|
+
- A classical cost function represented as a callable that returns a Qmod expression.
|
|
49
|
+
The callable should accept `QVar`s as arguments and use names matching the Model outputs.
|
|
50
|
+
initial_params: The initial parameters for the minimization.
|
|
51
|
+
This parameter must be of type `CReal` or `CArray`. The dictionary must contain a single key-value pair, where:
|
|
52
|
+
- The key is the name of the parameter.
|
|
53
|
+
- The value is either a float or a list of floats.
|
|
54
|
+
max_iteration: The maximum number of iterations for the minimization.
|
|
55
|
+
backend: The device (hardware or simulator) on which to run the quantum programs. Specified as "provider/device_id". Use the `get_backend_details` function to see supported devices.
|
|
56
|
+
quantile: The quantile to use for cost estimation.
|
|
57
|
+
tolerance: The tolerance for the minimization.
|
|
58
|
+
config: Provider-specific configuration, such as api keys
|
|
59
|
+
random_seed: Set this to increase determinism
|
|
60
|
+
transpilation_option: Advanced configuration for hardware-specific transpilation
|
|
61
|
+
verbosity: What level of information should be logged
|
|
62
|
+
Returns:
|
|
63
|
+
A list of tuples, each containing the estimated cost and the corresponding parameters for that iteration. `cost` is a float, and `parameters` is a dictionary matching the execution parameter format.
|
|
64
|
+
|
|
65
|
+
See Also:
|
|
66
|
+
The [Execution Tutorial](https://docs.classiq.io/latest/getting-started/classiq_tutorial/execution_tutorial_part2/) has examples on using this method in variational quantum algorithms.
|
|
67
|
+
More information about [Hamiltonians](https://docs.classiq.io/latest/qmod-reference/language-reference/classical-types/#hamiltonians).
|
|
68
|
+
"""
|
|
69
|
+
if backend is None:
|
|
70
|
+
backend = _DEFAULT_BACKEND_NAME
|
|
71
|
+
backend_preferences = _get_backend_preferences_from_specifier(backend, config or {})
|
|
72
|
+
|
|
73
|
+
ep = ExecutionPreferences(
|
|
74
|
+
backend_preferences=backend_preferences,
|
|
75
|
+
random_seed=create_random_seed() if random_seed is None else random_seed,
|
|
76
|
+
transpile_to_hardware=transpilation_option,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if verbosity != Verbosity.QUIET:
|
|
80
|
+
_logger.info(f"Submitting job to {backend}")
|
|
81
|
+
with ExecutionSession(qprog, execution_preferences=ep) as session:
|
|
82
|
+
job = session.submit_minimize(
|
|
83
|
+
cost_function, initial_params, max_iteration, quantile, tolerance
|
|
84
|
+
)
|
|
85
|
+
if verbosity != Verbosity.QUIET:
|
|
86
|
+
_logger.info(f"Job id: {job.id}")
|
|
87
|
+
result = job.get_minimization_result()
|
|
88
|
+
return session._minimize_result_to_result(
|
|
89
|
+
result=result, initial_params=initial_params
|
|
90
|
+
)
|