classiq 0.102.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/chemistry/op_utils.py +32 -0
- classiq/applications/combinatorial_optimization/combinatorial_problem.py +1 -1
- classiq/evaluators/qmod_annotated_expression.py +1 -1
- classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +1 -8
- classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +1 -1
- classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +2 -2
- classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +18 -29
- classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +1 -6
- classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +1 -7
- classiq/evaluators/qmod_type_inference/quantum_type_comparison.py +52 -0
- classiq/execution/all_hardware_devices.py +59 -1
- classiq/execution/execution_session.py +1 -1
- classiq/execution/functions/__init__.py +13 -0
- classiq/execution/functions/expectation_value.py +106 -0
- classiq/execution/functions/minimize.py +90 -0
- classiq/execution/functions/sample.py +76 -0
- classiq/execution/functions/state_vector.py +113 -0
- classiq/execution/functions/util/__init__.py +0 -0
- classiq/execution/functions/util/_logging.py +19 -0
- classiq/execution/functions/util/backend_preferences.py +188 -0
- classiq/execution/functions/util/constants.py +9 -0
- classiq/execution/functions/util/parse_provider_backend.py +90 -0
- classiq/interface/_version.py +1 -1
- classiq/interface/backend/backend_preferences.py +81 -0
- classiq/interface/backend/provider_config/providers/aqt.py +1 -1
- classiq/interface/backend/provider_config/providers/azure.py +1 -2
- classiq/interface/backend/provider_config/providers/ibm.py +1 -1
- classiq/interface/backend/quantum_backend_providers.py +14 -0
- classiq/interface/exceptions.py +0 -4
- classiq/interface/executor/result.py +9 -5
- classiq/interface/generator/arith/binary_ops.py +62 -2
- classiq/interface/generator/arith/number_utils.py +15 -6
- classiq/interface/generator/compiler_keywords.py +1 -0
- classiq/interface/generator/function_param_list.py +8 -2
- classiq/interface/generator/function_params.py +1 -1
- classiq/interface/generator/functions/builtins/internal_operators.py +5 -9
- classiq/interface/generator/functions/classical_type.py +60 -0
- classiq/interface/generator/functions/type_name.py +36 -0
- classiq/interface/generator/generated_circuit_data.py +0 -2
- classiq/interface/generator/transpiler_basis_gates.py +1 -0
- classiq/interface/generator/types/compilation_metadata.py +18 -0
- classiq/interface/hardware.py +2 -0
- classiq/interface/helpers/model_normalizer.py +42 -6
- classiq/interface/interface_version.py +1 -1
- classiq/interface/model/invert.py +8 -0
- classiq/interface/model/model.py +19 -0
- classiq/interface/model/model_visitor.py +4 -2
- classiq/interface/model/quantum_type.py +36 -0
- classiq/interface/model/statement_block.py +0 -4
- 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/model_expansions/capturing/captured_vars.py +16 -12
- classiq/model_expansions/function_builder.py +9 -1
- classiq/model_expansions/interpreters/base_interpreter.py +9 -8
- classiq/model_expansions/interpreters/generative_interpreter.py +9 -24
- classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -0
- classiq/model_expansions/quantum_operations/assignment_result_processor.py +132 -28
- classiq/model_expansions/quantum_operations/bind.py +4 -0
- classiq/model_expansions/quantum_operations/call_emitter.py +5 -35
- classiq/model_expansions/quantum_operations/emitter.py +1 -4
- classiq/model_expansions/quantum_operations/expression_evaluator.py +0 -3
- classiq/model_expansions/visitors/uncomputation_signature_inference.py +0 -9
- classiq/qmod/builtins/functions/__init__.py +21 -9
- classiq/qmod/builtins/functions/allocation.py +0 -36
- classiq/qmod/builtins/functions/arithmetic.py +183 -0
- classiq/qmod/builtins/functions/exponentiation.py +32 -2
- classiq/qmod/builtins/functions/gray_code.py +23 -0
- classiq/qmod/builtins/functions/mcx_func.py +10 -0
- classiq/qmod/builtins/operations.py +2 -38
- classiq/qmod/builtins/structs.py +22 -3
- classiq/qmod/native/pretty_printer.py +1 -12
- classiq/qmod/pretty_print/pretty_printer.py +1 -17
- classiq/qmod/qmod_parameter.py +4 -0
- classiq/qmod/qmod_variable.py +38 -63
- classiq/qmod/quantum_function.py +43 -7
- classiq/qmod/semantics/validation/function_name_collisions_validation.py +7 -4
- classiq/qmod/semantics/validation/model_validation.py +7 -2
- classiq/qmod/symbolic_type.py +4 -2
- classiq/qprog_to_cudaq.py +347 -0
- {classiq-0.102.0.dist-info → classiq-1.0.0.dist-info}/METADATA +4 -1
- {classiq-0.102.0.dist-info → classiq-1.0.0.dist-info}/RECORD +93 -76
- classiq/interface/generator/amplitude_loading.py +0 -103
- classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +0 -77
- {classiq-0.102.0.dist-info → classiq-1.0.0.dist-info}/WHEEL +0 -0
- {classiq-0.102.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()
|
|
@@ -74,6 +74,38 @@ def qubit_op_to_qmod(
|
|
|
74
74
|
)
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
def qmod_to_qubit_op(operator: SparsePauliOp) -> QubitOperator:
|
|
78
|
+
"""
|
|
79
|
+
Transforms Qmod's SparsePauliOp data structure to OpenFermion's QubitOperator data structure.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
operator (SparsePauliOp): The operator to be transformed
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
QubitOperator: The operator in OpenFermion's data structure
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
# Initiating the QubitOperator as the 0 operator
|
|
89
|
+
qo = QubitOperator()
|
|
90
|
+
for sparse_pauli_term in operator.terms:
|
|
91
|
+
# loop over all the IndexedPaulis
|
|
92
|
+
coeff = sparse_pauli_term.coefficient
|
|
93
|
+
if sparse_pauli_term.paulis:
|
|
94
|
+
qo.terms[
|
|
95
|
+
tuple(
|
|
96
|
+
[
|
|
97
|
+
(p.index, p.pauli.name)
|
|
98
|
+
for p in sparse_pauli_term.paulis # type: ignore[attr-defined]
|
|
99
|
+
if p.pauli is not Pauli.I
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
] = coeff
|
|
103
|
+
# Operator is the identity
|
|
104
|
+
else:
|
|
105
|
+
qo.terms[()] = coeff
|
|
106
|
+
return qo
|
|
107
|
+
|
|
108
|
+
|
|
77
109
|
_PAULIS_TO_XZ = {"I": (0, 0), "X": (1, 0), "Z": (0, 1), "Y": (1, 1)}
|
|
78
110
|
_XZ_TO_PAULIS = {(0, 0): "I", (1, 0): "X", (0, 1): "Z", (1, 1): "Y"}
|
|
79
111
|
|
|
@@ -207,7 +207,7 @@ class QmodAnnotatedExpression:
|
|
|
207
207
|
node = id(node)
|
|
208
208
|
return node in self._quantum_subscripts
|
|
209
209
|
|
|
210
|
-
def
|
|
210
|
+
def get_quantum_subscript(
|
|
211
211
|
self, node: ast.AST | QmodExprNodeId
|
|
212
212
|
) -> QuantumSubscriptAnnotation:
|
|
213
213
|
if isinstance(node, ast.AST):
|
|
@@ -77,7 +77,6 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
|
|
|
77
77
|
self,
|
|
78
78
|
expr_val: QmodAnnotatedExpression,
|
|
79
79
|
*,
|
|
80
|
-
treat_qnum_as_float: bool = False,
|
|
81
80
|
machine_precision: int = DEFAULT_MACHINE_PRECISION,
|
|
82
81
|
classical_struct_declarations: Sequence[StructDeclaration] | None = None,
|
|
83
82
|
enum_declarations: Sequence[EnumDeclaration] | None = None,
|
|
@@ -88,7 +87,6 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
|
|
|
88
87
|
scope: Mapping[str, Any] | None = None,
|
|
89
88
|
) -> None:
|
|
90
89
|
self._expr_val = expr_val
|
|
91
|
-
self._treat_qnum_as_float = treat_qnum_as_float
|
|
92
90
|
self._machine_precision = machine_precision
|
|
93
91
|
self._classical_struct_decls = nameables_to_dict(
|
|
94
92
|
classical_struct_declarations or []
|
|
@@ -114,9 +112,7 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
|
|
|
114
112
|
|
|
115
113
|
def visit_BinOp(self, node: ast.BinOp) -> None:
|
|
116
114
|
super().generic_visit(node)
|
|
117
|
-
eval_binary_op(
|
|
118
|
-
self._expr_val, node, self._treat_qnum_as_float, self._machine_precision
|
|
119
|
-
)
|
|
115
|
+
eval_binary_op(self._expr_val, node, self._machine_precision)
|
|
120
116
|
|
|
121
117
|
def visit_UnaryOp(self, node: ast.UnaryOp) -> None:
|
|
122
118
|
super().generic_visit(node)
|
|
@@ -164,7 +160,6 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
|
|
|
164
160
|
self._expr_val,
|
|
165
161
|
node,
|
|
166
162
|
func_name,
|
|
167
|
-
self._treat_qnum_as_float,
|
|
168
163
|
self._machine_precision,
|
|
169
164
|
)
|
|
170
165
|
return
|
|
@@ -256,7 +251,6 @@ class QmodExpressionEvaluator(ast.NodeVisitor):
|
|
|
256
251
|
def evaluate_qmod_expression(
|
|
257
252
|
expr: str,
|
|
258
253
|
*,
|
|
259
|
-
treat_qnum_as_float: bool = False,
|
|
260
254
|
machine_precision: int = DEFAULT_MACHINE_PRECISION,
|
|
261
255
|
classical_struct_declarations: Sequence[StructDeclaration] | None = None,
|
|
262
256
|
enum_declarations: Sequence[EnumDeclaration] | None = None,
|
|
@@ -270,7 +264,6 @@ def evaluate_qmod_expression(
|
|
|
270
264
|
expr_value = QmodAnnotatedExpression(expr_ast)
|
|
271
265
|
QmodExpressionEvaluator(
|
|
272
266
|
expr_value,
|
|
273
|
-
treat_qnum_as_float=treat_qnum_as_float,
|
|
274
267
|
machine_precision=machine_precision,
|
|
275
268
|
classical_struct_declarations=classical_struct_declarations,
|
|
276
269
|
enum_declarations=enum_declarations,
|
|
@@ -122,7 +122,7 @@ class _InverseVarMaskTransformer(OutOfPlaceNodeTransformer):
|
|
|
122
122
|
|
|
123
123
|
class _SympyCompatibilityTransformer(OutOfPlaceNodeTransformer):
|
|
124
124
|
def visit_BoolOp(self, node: ast.BoolOp) -> ast.Call:
|
|
125
|
-
if len(node.values)
|
|
125
|
+
if len(node.values) < 2:
|
|
126
126
|
raise ClassiqInternalExpansionError
|
|
127
127
|
node = cast(ast.BoolOp, self.generic_visit(node))
|
|
128
128
|
if isinstance(node.op, ast.Or):
|
|
@@ -107,7 +107,7 @@ def _eval_type_attribute(
|
|
|
107
107
|
if isinstance(subject_type, QuantumNumeric):
|
|
108
108
|
if attr == "is_signed":
|
|
109
109
|
expr_val.set_type(node, Bool())
|
|
110
|
-
if subject_type.
|
|
110
|
+
if subject_type.has_constant_sign:
|
|
111
111
|
expr_val.set_value(node, subject_type.sign_value)
|
|
112
112
|
_remove_quantum_var(expr_val, subject)
|
|
113
113
|
elif subject_type.has_size_in_bits:
|
|
@@ -120,7 +120,7 @@ def _eval_type_attribute(
|
|
|
120
120
|
return
|
|
121
121
|
if attr == "fraction_digits":
|
|
122
122
|
expr_val.set_type(node, Integer())
|
|
123
|
-
if subject_type.
|
|
123
|
+
if subject_type.has_constant_fraction_digits:
|
|
124
124
|
expr_val.set_value(node, subject_type.fraction_digits_value)
|
|
125
125
|
_remove_quantum_var(expr_val, subject)
|
|
126
126
|
elif subject_type.has_size_in_bits:
|
|
@@ -50,7 +50,6 @@ def _validate_binary_op(
|
|
|
50
50
|
op: ast.AST,
|
|
51
51
|
left_type: QmodType,
|
|
52
52
|
right_type: QmodType,
|
|
53
|
-
treat_qnum_as_float: bool,
|
|
54
53
|
) -> None:
|
|
55
54
|
if not _binary_op_allowed(left_type, right_type, op):
|
|
56
55
|
raise ClassiqExpansionError(
|
|
@@ -76,20 +75,19 @@ def _validate_binary_op(
|
|
|
76
75
|
f"Binary operation {type(op).__name__!r} is not supported"
|
|
77
76
|
)
|
|
78
77
|
|
|
79
|
-
if
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
78
|
+
if isinstance(op, ast.FloorDiv) and (
|
|
79
|
+
not is_classical_type(left_type) or not is_classical_type(right_type)
|
|
80
|
+
):
|
|
81
|
+
raise ClassiqExpansionError(
|
|
82
|
+
f"{type(op).__name__!r} with quantum variables is not supported"
|
|
83
|
+
)
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
if not is_classical_type(right_type) and isinstance(
|
|
86
|
+
op, (ast.Div, ast.Mod, ast.Pow, ast.LShift, ast.RShift)
|
|
87
|
+
):
|
|
88
|
+
raise ClassiqExpansionError(
|
|
89
|
+
f"Right-hand side of binary operation {type(op).__name__!r} must be classical numeric value"
|
|
90
|
+
)
|
|
93
91
|
|
|
94
92
|
|
|
95
93
|
def _infer_binary_op_type(
|
|
@@ -98,7 +96,6 @@ def _infer_binary_op_type(
|
|
|
98
96
|
left_type: QmodType,
|
|
99
97
|
right_type: QmodType,
|
|
100
98
|
machine_precision: int,
|
|
101
|
-
treat_qnum_as_float: bool,
|
|
102
99
|
) -> QmodType:
|
|
103
100
|
op = node.op
|
|
104
101
|
|
|
@@ -113,19 +110,13 @@ def _infer_binary_op_type(
|
|
|
113
110
|
return Integer()
|
|
114
111
|
return Real()
|
|
115
112
|
|
|
116
|
-
left_attrs = get_numeric_attrs(
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
right_attrs = get_numeric_attrs(
|
|
120
|
-
expr_val, node.right, right_type, machine_precision, treat_qnum_as_float
|
|
121
|
-
)
|
|
113
|
+
left_attrs = get_numeric_attrs(expr_val, node.left, left_type, machine_precision)
|
|
114
|
+
right_attrs = get_numeric_attrs(expr_val, node.right, right_type, machine_precision)
|
|
122
115
|
|
|
123
116
|
if left_attrs is None or right_attrs is None:
|
|
124
117
|
return QuantumNumeric()
|
|
125
118
|
|
|
126
|
-
right_value = get_classical_value_for_arithmetic(
|
|
127
|
-
expr_val, node.right, right_type, treat_qnum_as_float
|
|
128
|
-
)
|
|
119
|
+
right_value = get_classical_value_for_arithmetic(expr_val, node.right, right_type)
|
|
129
120
|
|
|
130
121
|
if isinstance(op, ast.Add):
|
|
131
122
|
result_attrs = compute_result_attrs_add(
|
|
@@ -157,14 +148,14 @@ def _infer_binary_op_type(
|
|
|
157
148
|
return QuantumNumeric()
|
|
158
149
|
|
|
159
150
|
elif isinstance(op, ast.Mod):
|
|
160
|
-
if right_value is None
|
|
151
|
+
if right_value is None:
|
|
161
152
|
return QuantumNumeric()
|
|
162
153
|
result_attrs = compute_result_attrs_modulo(
|
|
163
154
|
left_attrs, right_attrs, machine_precision
|
|
164
155
|
)
|
|
165
156
|
|
|
166
157
|
elif isinstance(op, ast.Pow):
|
|
167
|
-
if right_value is None
|
|
158
|
+
if right_value is None:
|
|
168
159
|
return QuantumNumeric()
|
|
169
160
|
result_attrs = compute_result_attrs_power(
|
|
170
161
|
left_attrs, right_attrs, machine_precision
|
|
@@ -252,7 +243,6 @@ def _eval_binary_op_constant(
|
|
|
252
243
|
def eval_binary_op(
|
|
253
244
|
expr_val: QmodAnnotatedExpression,
|
|
254
245
|
node: ast.BinOp,
|
|
255
|
-
treat_qnum_as_float: bool,
|
|
256
246
|
machine_precision: int,
|
|
257
247
|
) -> None:
|
|
258
248
|
left = node.left
|
|
@@ -261,7 +251,7 @@ def eval_binary_op(
|
|
|
261
251
|
|
|
262
252
|
left_type = expr_val.get_type(left)
|
|
263
253
|
right_type = expr_val.get_type(right)
|
|
264
|
-
_validate_binary_op(op, left_type, right_type
|
|
254
|
+
_validate_binary_op(op, left_type, right_type)
|
|
265
255
|
|
|
266
256
|
inferred_type = _infer_binary_op_type(
|
|
267
257
|
expr_val,
|
|
@@ -269,7 +259,6 @@ def eval_binary_op(
|
|
|
269
259
|
left_type,
|
|
270
260
|
right_type,
|
|
271
261
|
machine_precision,
|
|
272
|
-
treat_qnum_as_float,
|
|
273
262
|
)
|
|
274
263
|
expr_val.set_type(node, inferred_type)
|
|
275
264
|
|
|
@@ -31,7 +31,6 @@ def _infer_min_max_op_type(
|
|
|
31
31
|
node: ast.Call,
|
|
32
32
|
func_name: str,
|
|
33
33
|
args_types: list[QmodType],
|
|
34
|
-
treat_qnum_as_float: bool,
|
|
35
34
|
machine_precision: int,
|
|
36
35
|
) -> QmodType:
|
|
37
36
|
if all(is_classical_type(arg_type) for arg_type in args_types):
|
|
@@ -41,9 +40,7 @@ def _infer_min_max_op_type(
|
|
|
41
40
|
|
|
42
41
|
args_attrs: list[NumericAttributes] = []
|
|
43
42
|
for arg, arg_type in zip(node.args, args_types):
|
|
44
|
-
attrs = get_numeric_attrs(
|
|
45
|
-
expr_val, arg, arg_type, machine_precision, treat_qnum_as_float
|
|
46
|
-
)
|
|
43
|
+
attrs = get_numeric_attrs(expr_val, arg, arg_type, machine_precision)
|
|
47
44
|
if attrs is None:
|
|
48
45
|
return QuantumNumeric()
|
|
49
46
|
args_attrs.append(attrs)
|
|
@@ -62,7 +59,6 @@ def eval_min_max_op(
|
|
|
62
59
|
expr_val: QmodAnnotatedExpression,
|
|
63
60
|
node: ast.Call,
|
|
64
61
|
func_name: str,
|
|
65
|
-
treat_qnum_as_float: bool,
|
|
66
62
|
machine_precision: int,
|
|
67
63
|
) -> None:
|
|
68
64
|
if len(node.args) < 1:
|
|
@@ -79,7 +75,6 @@ def eval_min_max_op(
|
|
|
79
75
|
node,
|
|
80
76
|
func_name,
|
|
81
77
|
args_types,
|
|
82
|
-
treat_qnum_as_float,
|
|
83
78
|
machine_precision,
|
|
84
79
|
)
|
|
85
80
|
expr_val.set_type(node, inferred_type)
|
|
@@ -21,14 +21,11 @@ def get_numeric_attrs(
|
|
|
21
21
|
node: ast.AST,
|
|
22
22
|
qmod_type: QmodType,
|
|
23
23
|
machine_precision: int,
|
|
24
|
-
treat_qnum_as_float: bool,
|
|
25
24
|
) -> NumericAttributes | None:
|
|
26
25
|
if isinstance(qmod_type, Bool):
|
|
27
26
|
return NumericAttributes.from_bounds(0, 1, 0, machine_precision)
|
|
28
27
|
if is_classical_type(qmod_type):
|
|
29
|
-
value = get_classical_value_for_arithmetic(
|
|
30
|
-
expr_val, node, qmod_type, treat_qnum_as_float
|
|
31
|
-
)
|
|
28
|
+
value = get_classical_value_for_arithmetic(expr_val, node, qmod_type)
|
|
32
29
|
if value is None:
|
|
33
30
|
return None
|
|
34
31
|
return NumericAttributes.from_constant(value, machine_precision)
|
|
@@ -44,7 +41,6 @@ def get_classical_value_for_arithmetic(
|
|
|
44
41
|
expr_val: QmodAnnotatedExpression,
|
|
45
42
|
node: ast.AST,
|
|
46
43
|
qmod_type: QmodType,
|
|
47
|
-
treat_qnum_as_float: bool,
|
|
48
44
|
) -> float | None:
|
|
49
45
|
if not is_classical_type(qmod_type):
|
|
50
46
|
return None
|
|
@@ -55,8 +51,6 @@ def get_classical_value_for_arithmetic(
|
|
|
55
51
|
if isinstance(value, sympy.Basic):
|
|
56
52
|
value = get_sympy_val(value)
|
|
57
53
|
if not isinstance(value, (int, float)):
|
|
58
|
-
if treat_qnum_as_float and isinstance(value, complex):
|
|
59
|
-
return None
|
|
60
54
|
raise ClassiqExpansionError(
|
|
61
55
|
"Arithmetic of quantum variables and non-real values is not supported"
|
|
62
56
|
)
|