classiq 0.94.2__py3-none-any.whl → 0.95.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.

Potentially problematic release.


This version of classiq might be problematic. Click here for more details.

Files changed (56) hide show
  1. classiq/_internals/authentication/auth0.py +32 -3
  2. classiq/_internals/authentication/authorization_code.py +9 -0
  3. classiq/_internals/authentication/authorization_flow.py +41 -0
  4. classiq/_internals/authentication/device.py +31 -50
  5. classiq/_internals/authentication/hybrid_flow.py +19 -0
  6. classiq/_internals/authentication/token_manager.py +5 -4
  7. classiq/applications/__init__.py +2 -2
  8. classiq/applications/qsp/qsp.py +6 -5
  9. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +10 -0
  10. classiq/interface/_version.py +1 -1
  11. classiq/interface/backend/backend_preferences.py +11 -6
  12. classiq/interface/exceptions.py +0 -4
  13. classiq/interface/generator/application_apis/__init__.py +0 -1
  14. classiq/interface/generator/function_param_list.py +0 -2
  15. classiq/interface/generator/generated_circuit_data.py +1 -6
  16. classiq/interface/generator/quantum_program.py +0 -4
  17. classiq/interface/generator/transpiler_basis_gates.py +3 -0
  18. classiq/interface/generator/types/builtin_enum_declarations.py +0 -9
  19. classiq/interface/interface_version.py +1 -1
  20. classiq/interface/model/block.py +4 -0
  21. classiq/interface/model/classical_if.py +4 -0
  22. classiq/interface/model/control.py +7 -0
  23. classiq/interface/model/invert.py +4 -0
  24. classiq/interface/model/model_visitor.py +40 -1
  25. classiq/interface/model/power.py +4 -0
  26. classiq/interface/model/quantum_statement.py +8 -1
  27. classiq/interface/model/repeat.py +4 -0
  28. classiq/interface/model/skip_control.py +4 -0
  29. classiq/interface/model/within_apply_operation.py +4 -0
  30. classiq/model_expansions/interpreters/base_interpreter.py +1 -1
  31. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +2 -1
  32. classiq/model_expansions/visitors/symbolic_param_inference.py +3 -3
  33. classiq/model_expansions/visitors/uncomputation_signature_inference.py +14 -3
  34. classiq/open_library/functions/__init__.py +4 -1
  35. classiq/open_library/functions/amplitude_loading.py +66 -0
  36. classiq/open_library/functions/lookup_table.py +22 -9
  37. classiq/open_library/functions/modular_exponentiation.py +5 -8
  38. classiq/open_library/functions/qsvt.py +4 -4
  39. classiq/qmod/builtins/classical_execution_primitives.py +0 -12
  40. classiq/qmod/builtins/enums.py +15 -17
  41. classiq/qmod/builtins/functions/__init__.py +3 -5
  42. classiq/qmod/builtins/functions/mcx.py +7 -0
  43. classiq/qmod/builtins/operations.py +60 -22
  44. classiq/qmod/builtins/structs.py +22 -33
  45. classiq/qmod/semantics/annotation/call_annotation.py +3 -3
  46. classiq/qmod/semantics/error_manager.py +7 -8
  47. {classiq-0.94.2.dist-info → classiq-0.95.0.dist-info}/METADATA +1 -1
  48. {classiq-0.94.2.dist-info → classiq-0.95.0.dist-info}/RECORD +50 -51
  49. {classiq-0.94.2.dist-info → classiq-0.95.0.dist-info}/WHEEL +1 -1
  50. classiq/applications/qsvm/__init__.py +0 -8
  51. classiq/applications/qsvm/qsvm.py +0 -11
  52. classiq/interface/applications/qsvm.py +0 -114
  53. classiq/interface/generator/application_apis/qsvm_declarations.py +0 -6
  54. classiq/interface/generator/qsvm.py +0 -96
  55. classiq/qmod/builtins/functions/qsvm.py +0 -24
  56. {classiq-0.94.2.dist-info → classiq-0.95.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import urllib.parse
2
+ import warnings
2
3
  from dataclasses import dataclass
3
4
  from typing import Any
4
5
 
@@ -20,6 +21,7 @@ class AuthSettings(BaseSettings):
20
21
  default="f6721qMOVoDAOVkzrv8YaWassRKSFX6Y",
21
22
  validation_alias="CLASSIQ_AUTH_CLIENT_ID",
22
23
  )
24
+ organization: str = Field(default="", validation_alias="CLASSIQ_AUTH_ORGANIZATION")
23
25
 
24
26
  model_config = SettingsConfigDict(extra="allow")
25
27
 
@@ -53,6 +55,10 @@ class Auth0:
53
55
  def _client_id(self) -> str:
54
56
  return self._auth_settings.client_id
55
57
 
58
+ @property
59
+ def _organization(self) -> str:
60
+ return self._auth_settings.organization
61
+
56
62
  async def _make_request(
57
63
  self,
58
64
  url: str,
@@ -76,13 +82,20 @@ class Auth0:
76
82
  f"Request to Auth0 failed with error code {code}: {data.get('error')}"
77
83
  )
78
84
 
79
- async def get_device_data(self, get_refresh_token: bool = True) -> dict[str, Any]:
85
+ async def get_device_data(
86
+ self, require_refresh_token: bool = True
87
+ ) -> dict[str, Any]:
80
88
  payload = {
81
- "client_id": self._auth_settings.client_id,
89
+ "client_id": self._client_id,
82
90
  "audience": self._auth_settings.audience,
83
91
  }
84
- if get_refresh_token:
92
+ if require_refresh_token:
85
93
  payload["scope"] = "offline_access"
94
+ if self._organization:
95
+ warnings.warn(
96
+ "Organizations are not supported in device auth flow.",
97
+ stacklevel=1,
98
+ )
86
99
 
87
100
  return await self._make_request(
88
101
  url="/oauth/device/code",
@@ -102,6 +115,22 @@ class Auth0:
102
115
  allow_error=codes.FORBIDDEN,
103
116
  )
104
117
 
118
+ def get_authorize_url(
119
+ self, redirect_uri: str, require_refresh_token: bool = True
120
+ ) -> str:
121
+ params = {
122
+ "client_id": self._client_id,
123
+ "response_type": "code",
124
+ "audience": self._auth_settings.audience,
125
+ "redirect_uri": redirect_uri,
126
+ }
127
+ if require_refresh_token:
128
+ params["scope"] = "offline_access"
129
+ if self._organization:
130
+ # Otherwise, let the Auth0 handle
131
+ params["organization"] = self._organization
132
+ return f"{self._base_url}/authorize?{urllib.parse.urlencode(params)}"
133
+
105
134
  async def refresh_access_token(self, refresh_token: str) -> Tokens:
106
135
  # TODO handle failure
107
136
  payload = {
@@ -0,0 +1,9 @@
1
+ from classiq._internals.authentication.authorization_flow import AuthorizationFlow
2
+
3
+
4
+ class AuthorizationCodeFlow(AuthorizationFlow):
5
+ async def authorize(self, redirect_uri: str) -> None:
6
+ auth_url = self.auth0_client.get_authorize_url(
7
+ redirect_uri, self.require_refresh_token
8
+ )
9
+ self.open_url(auth_url)
@@ -0,0 +1,41 @@
1
+ import webbrowser
2
+ from typing import Any
3
+
4
+ from classiq.interface.exceptions import ClassiqAuthenticationError
5
+
6
+ from classiq._internals.authentication.auth0 import Auth0, Tokens
7
+
8
+
9
+ class AuthorizationFlow:
10
+ def __init__(self, require_refresh_token: bool = True, text_only: bool = False):
11
+ self.require_refresh_token = require_refresh_token
12
+ self.text_only = text_only
13
+ self.auth0_client = Auth0()
14
+
15
+ async def get_tokens(self) -> Tokens:
16
+ raise NotImplementedError
17
+
18
+ def handle_ready_data(self, data: dict[str, Any]) -> Tokens:
19
+ access_token: str | None = data.get("access_token") or None
20
+ # If refresh token was not requested, this would be None
21
+ refresh_token: str | None = data.get("refresh_token") or None
22
+
23
+ if access_token is None or (
24
+ self.require_refresh_token is True and refresh_token is None
25
+ ):
26
+ raise ClassiqAuthenticationError(
27
+ "Token generation failed for unknown reason (missing access token or refresh token)."
28
+ )
29
+
30
+ return Tokens(access_token=access_token, refresh_token=refresh_token)
31
+
32
+ def open_url(self, url: str) -> None:
33
+ if self.text_only:
34
+ print( # noqa: T201
35
+ f"Please visit this URL from any trusted device to authenticate: {url}"
36
+ )
37
+ else:
38
+ webbrowser.open(url)
39
+ print( # noqa: T201
40
+ f"If a browser doesn't automatically open, please visit this URL from any trusted device to authenticate: {url}"
41
+ )
@@ -1,6 +1,6 @@
1
1
  import asyncio
2
- import webbrowser
3
2
  from collections.abc import Iterable
3
+ from dataclasses import dataclass
4
4
  from datetime import timedelta
5
5
  from typing import Any, TypeVar
6
6
 
@@ -10,74 +10,55 @@ from classiq.interface.exceptions import (
10
10
  )
11
11
 
12
12
  from classiq._internals.async_utils import poll_for
13
- from classiq._internals.authentication.auth0 import Auth0, Tokens
13
+ from classiq._internals.authentication.auth0 import Tokens
14
+ from classiq._internals.authentication.authorization_flow import AuthorizationFlow
14
15
 
15
16
  T = TypeVar("T")
16
17
 
17
18
 
18
- class DeviceRegistrar:
19
+ @dataclass
20
+ class DeviceData:
21
+ user_code: str
22
+ device_code: str
23
+ interval: float
24
+ expires_in: float
25
+ verification_uri: str
26
+ verification_uri_complete: str
27
+
28
+
29
+ class DeviceCodeFlow(AuthorizationFlow):
19
30
  _TIMEOUT_ERROR = (
20
31
  "Device registration timed out. Please re-initiate the flow and "
21
32
  "authorize the device within the timeout."
22
33
  )
23
34
  _TIMEOUT_SEC: float = timedelta(minutes=15).total_seconds()
24
35
 
25
- @classmethod
26
- async def register(
27
- cls, get_refresh_token: bool = True, text_only: bool = False
28
- ) -> Tokens:
29
- auth0_client = Auth0()
30
- data: dict[str, Any] = await auth0_client.get_device_data(
31
- get_refresh_token=get_refresh_token
36
+ async def get_device_data(self) -> DeviceData:
37
+ device_data: dict[str, Any] = await self.auth0_client.get_device_data(
38
+ require_refresh_token=self.require_refresh_token
32
39
  )
40
+ return DeviceData(**device_data)
33
41
 
34
- print(f"Your user code: {data['user_code']}") # noqa: T201
35
- verification_url = data["verification_uri_complete"]
36
- print( # noqa: T201
37
- f"If a browser doesn't automatically open, please visit this URL from any trusted device: {verification_url}"
38
- )
39
- if not text_only:
40
- webbrowser.open(verification_url)
41
- timeout = min(data["expires_in"], cls._TIMEOUT_SEC)
42
- return await cls._poll_tokens(
43
- auth0_client=auth0_client,
44
- device_code=data["device_code"],
45
- interval=data["interval"],
42
+ async def poll_tokens(self, device_data: DeviceData) -> Tokens:
43
+ interval = device_data.interval
44
+ timeout = min(device_data.expires_in, self._TIMEOUT_SEC)
45
+ return await self._poll_tokens(
46
+ device_code=device_data.device_code,
47
+ interval=interval,
46
48
  timeout=timeout,
47
- get_refresh_token=get_refresh_token,
48
49
  )
49
50
 
50
- @classmethod
51
- def _handle_ready_data(
52
- cls, data: dict[str, Any], get_refresh_token: bool
53
- ) -> Tokens:
54
- access_token: str | None = data.get("access_token")
55
- # If refresh token was not requested, this would be None
56
- refresh_token: str | None = data.get("refresh_token")
57
-
58
- if access_token is None or (
59
- get_refresh_token is True and refresh_token is None
60
- ):
61
- raise ClassiqAuthenticationError(
62
- "Token generation failed for unknown reason."
63
- )
64
-
65
- return Tokens(access_token=access_token, refresh_token=refresh_token)
66
-
67
- @classmethod
68
51
  async def _poll_tokens(
69
- cls,
70
- auth0_client: Auth0,
52
+ self,
71
53
  device_code: str,
72
- interval: int,
54
+ interval: float,
73
55
  timeout: float,
74
- get_refresh_token: bool = True,
75
56
  ) -> Tokens:
76
57
  async def poller() -> dict[str, Any]:
77
58
  nonlocal device_code
78
- return await auth0_client.poll_tokens(device_code=device_code)
59
+ return await self.auth0_client.poll_tokens(device_code=device_code)
79
60
 
80
- def interval_coro() -> Iterable[int]:
61
+ def interval_coro() -> Iterable[float]:
81
62
  nonlocal interval
82
63
  while True:
83
64
  yield interval
@@ -88,14 +69,14 @@ class DeviceRegistrar:
88
69
  ):
89
70
  error_code: str | None = data.get("error")
90
71
  if error_code is None:
91
- return cls._handle_ready_data(data, get_refresh_token)
72
+ return self.handle_ready_data(data)
92
73
  elif error_code == "authorization_pending":
93
74
  pass
94
75
  elif error_code == "slow_down":
95
76
  # This value is used by poll_for via interval_coro
96
77
  interval *= 2
97
78
  elif error_code == "expired_token":
98
- raise ClassiqExpiredTokenError(cls._TIMEOUT_ERROR)
79
+ raise ClassiqExpiredTokenError(self._TIMEOUT_ERROR)
99
80
  elif error_code == "access_denied":
100
81
  error_description = data.get("error_description")
101
82
  if error_description is None:
@@ -109,4 +90,4 @@ class DeviceRegistrar:
109
90
  f"Device registration failed with an unknown error: {error_code}."
110
91
  )
111
92
  else:
112
- raise ClassiqAuthenticationError(cls._TIMEOUT_ERROR)
93
+ raise ClassiqAuthenticationError(self._TIMEOUT_ERROR)
@@ -0,0 +1,19 @@
1
+ from classiq._internals.authentication.auth0 import Tokens
2
+ from classiq._internals.authentication.authorization_code import AuthorizationCodeFlow
3
+ from classiq._internals.authentication.authorization_flow import AuthorizationFlow
4
+ from classiq._internals.authentication.device import DeviceCodeFlow
5
+
6
+
7
+ class HybridFlow(AuthorizationFlow):
8
+ def __init__(
9
+ self, require_refresh_token: bool = True, text_only: bool = False
10
+ ) -> None:
11
+ super().__init__(require_refresh_token, text_only)
12
+ self.device_flow = DeviceCodeFlow(require_refresh_token, text_only)
13
+ self.auth_code_flow = AuthorizationCodeFlow(require_refresh_token, text_only)
14
+
15
+ async def get_tokens(self) -> Tokens:
16
+ device_data = await self.device_flow.get_device_data()
17
+ await self.auth_code_flow.authorize(device_data.verification_uri_complete)
18
+ print(f"Your user code: {device_data.user_code}") # noqa: T201
19
+ return await self.device_flow.poll_tokens(device_data)
@@ -10,8 +10,8 @@ from classiq.interface.exceptions import (
10
10
  )
11
11
 
12
12
  from classiq._internals.authentication import password_manager as pm
13
- from classiq._internals.authentication.auth0 import Auth0
14
- from classiq._internals.authentication.device import DeviceRegistrar, Tokens
13
+ from classiq._internals.authentication.auth0 import Auth0, Tokens
14
+ from classiq._internals.authentication.hybrid_flow import HybridFlow
15
15
  from classiq._internals.config import Configuration
16
16
 
17
17
  PASSWORD_MANAGERS: Sequence[type[pm.PasswordManager]] = [
@@ -126,7 +126,8 @@ class TokenManager:
126
126
  async def _authentication_helper(self) -> None:
127
127
  # TODO: consider using refresh token rotation
128
128
  # (https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation)
129
- tokens = await DeviceRegistrar.register(
130
- get_refresh_token=True, text_only=self._config.text_only
129
+ authorization_flow = HybridFlow(
130
+ require_refresh_token=True, text_only=self._config.text_only
131
131
  )
132
+ tokens = await authorization_flow.get_tokens()
132
133
  self._save_tokens(tokens, force_override_refresh_token=True)
@@ -1,6 +1,6 @@
1
- from classiq.applications import chemistry, combinatorial_optimization, qsp, qsvm
1
+ from classiq.applications import chemistry, combinatorial_optimization, qsp
2
2
 
3
- __all__ = ["chemistry", "combinatorial_optimization", "qsp", "qsvm"]
3
+ __all__ = ["chemistry", "combinatorial_optimization", "qsp"]
4
4
 
5
5
 
6
6
  _NON_IMPORTED_PUBLIC_SUBMODULES = ["qnn"]
@@ -50,7 +50,7 @@ def qsvt_phases(
50
50
  ) -> np.ndarray:
51
51
  r"""
52
52
  Get QSVT phases that will generate the given Chebyshev polynomial.
53
- The phases are ready to be used in `qsvt` and `qsvt_lcu` functions in the classiq library. The convetion
53
+ The phases are ready to be used in `qsvt` and `qsvt_lcu` functions in the classiq library. The convention
54
54
  is the reflection signal operator, and the measurement basis is the hadamard basis (see https://arxiv.org/abs/2105.02859
55
55
  APPENDIX A.).
56
56
  The current implementation is using the pyqsp package, based on techniques in https://arxiv.org/abs/2003.02831.
@@ -118,7 +118,7 @@ def qsvt_phases(
118
118
 
119
119
  def _plot_qsp_approx(
120
120
  poly_cheb: np.ndarray,
121
- f_target: Callable[[np.ndarray], np.ndarray],
121
+ f_target: Callable[[float], complex],
122
122
  interval: tuple[float, float] = (-1, 1),
123
123
  ) -> None:
124
124
  from matplotlib import pyplot as plt
@@ -126,7 +126,7 @@ def _plot_qsp_approx(
126
126
  grid_full = np.linspace(-1, 1, 3000)
127
127
  grid_interval = np.linspace(interval[0], interval[1], 3000)
128
128
 
129
- y_target = f_target(grid_interval)
129
+ y_target = np.vectorize(f_target, otypes=[float])(grid_interval)
130
130
  y_approx = np.polynomial.Chebyshev(poly_cheb)(grid_full)
131
131
 
132
132
  # Plot
@@ -153,7 +153,7 @@ def _plot_qsp_approx(
153
153
 
154
154
 
155
155
  def qsp_approximate(
156
- f_target: Callable[[np.ndarray], np.ndarray],
156
+ f_target: Callable[[float], complex],
157
157
  degree: int,
158
158
  parity: int | None = None,
159
159
  interval: tuple[float, float] = (-1, 1),
@@ -193,7 +193,8 @@ def qsp_approximate(
193
193
  # Select grid points for the objective in [w_min, w_max]
194
194
  xj_obj = xj_full[(xj_full >= interval[0]) & (xj_full <= interval[1])]
195
195
 
196
- yj_obj = f_target(xj_obj)
196
+ yj_obj = np.vectorize(f_target, otypes=[float])(xj_obj)
197
+
197
198
  # heuristic verification
198
199
  bound = min(1, bound)
199
200
  assert (
@@ -7,6 +7,7 @@ import sympy
7
7
  from classiq.interface.exceptions import (
8
8
  ClassiqExpansionError,
9
9
  ClassiqInternalExpansionError,
10
+ ClassiqValueError,
10
11
  )
11
12
  from classiq.interface.generator.functions.classical_function_declaration import (
12
13
  ClassicalFunctionDeclaration,
@@ -208,6 +209,15 @@ def try_eval_sympy_function(
208
209
 
209
210
  def try_eval_builtin_function(
210
211
  expr_val: QmodAnnotatedExpression, node: ast.Call, func_name: str
212
+ ) -> bool:
213
+ try:
214
+ return _try_eval_builtin_function(expr_val, node, func_name)
215
+ except ValueError as e:
216
+ raise ClassiqValueError(str(e)) from e
217
+
218
+
219
+ def _try_eval_builtin_function(
220
+ expr_val: QmodAnnotatedExpression, node: ast.Call, func_name: str
211
221
  ) -> bool:
212
222
  args_are_int = all(isinstance(expr_val.get_type(arg), Integer) for arg in node.args)
213
223
  args_are_real = all(
@@ -3,5 +3,5 @@ from packaging.version import Version
3
3
  # This file was generated automatically
4
4
  # Please don't track in version control (DONTTRACK)
5
5
 
6
- SEMVER_VERSION = '0.94.2'
6
+ SEMVER_VERSION = '0.95.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -172,10 +172,11 @@ class AwsBackendPreferences(BackendPreferences):
172
172
  backend_service_provider (ProviderTypeVendor.AMAZON_BRAKET):
173
173
  The service provider for the backend, which is Amazon Braket.
174
174
 
175
- aws_role_arn (pydantic_backend.PydanticAwsRoleArn):
176
- The Amazon Resource Name (ARN) of the role that will be assumed for execution
177
- on your Braket account. This is a required field and should be provided to allow
178
- secure and authorized access to AWS resources.
175
+ aws_access_key_id (str):
176
+ The access key id of AWS user with full braket access
177
+
178
+ aws_secret_access_key (str):
179
+ The secret key assigned to the access key id for the user with full braket access.
179
180
 
180
181
  s3_bucket_name (str):
181
182
  The name of the S3 bucket where results and other related data will be stored.
@@ -193,9 +194,13 @@ class AwsBackendPreferences(BackendPreferences):
193
194
  backend_service_provider: ProviderTypeVendor.AMAZON_BRAKET = pydantic.Field(
194
195
  default=ProviderVendor.AMAZON_BRAKET
195
196
  )
196
- aws_role_arn: str | None = pydantic.Field(
197
+ aws_access_key_id: str | None = pydantic.Field(
198
+ default=None,
199
+ description="Key id assigned to user with credentials to access Braket service",
200
+ )
201
+ aws_secret_access_key: str | None = pydantic.Field(
197
202
  default=None,
198
- description="ARN of the role to be assumed for execution on your Braket account.",
203
+ description="Secret access key assigned to user with credentials to access Braket service",
199
204
  )
200
205
  s3_bucket_name: str | None = pydantic.Field(
201
206
  default=None, description="S3 Bucket Name"
@@ -95,10 +95,6 @@ class ClassiqQFuncError(ClassiqValueError):
95
95
  pass
96
96
 
97
97
 
98
- class ClassiqQSVMError(ClassiqValueError):
99
- pass
100
-
101
-
102
98
  class ClassiqQNNError(ClassiqValueError):
103
99
  pass
104
100
 
@@ -4,6 +4,5 @@ from classiq.interface.generator.builtin_api_builder import (
4
4
 
5
5
  from .arithmetic_declarations import * # noqa: F403
6
6
  from .combinatorial_optimization_declarations import * # noqa: F403
7
- from .qsvm_declarations import * # noqa: F403
8
7
 
9
8
  populate_builtin_declarations(vars().values())
@@ -42,7 +42,6 @@ from classiq.interface.generator.hardware_efficient_ansatz import (
42
42
  from classiq.interface.generator.identity import Identity
43
43
  from classiq.interface.generator.mcu import Mcu
44
44
  from classiq.interface.generator.mcx import Mcx
45
- from classiq.interface.generator.qsvm import QSVMFeatureMap
46
45
  from classiq.interface.generator.randomized_benchmarking import RandomizedBenchmarking
47
46
  from classiq.interface.generator.reset import Reset
48
47
  from classiq.interface.generator.standard_gates.standard_gates_param_list import (
@@ -108,7 +107,6 @@ function_param_library: FunctionParamLibrary = FunctionParamLibrary(
108
107
  RandomizedBenchmarking,
109
108
  UGate,
110
109
  AmplitudeLoading,
111
- QSVMFeatureMap,
112
110
  HadamardTransform,
113
111
  Copy,
114
112
  Reset,
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  import re
3
- from typing import Literal, Optional, TypeAlias
3
+ from typing import Literal, TypeAlias
4
4
  from uuid import UUID
5
5
 
6
6
  import pydantic
@@ -188,8 +188,6 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
188
188
  back_refs: StatementBlock = Field(default_factory=list)
189
189
 
190
190
  model_config = ConfigDict(extra="allow")
191
- # Temporary field to store the override debug info for parallel old/new visualization
192
- override_debug_info: Optional["FunctionDebugInfoInterface"] = None
193
191
 
194
192
  @property
195
193
  def is_allocate_or_free(self) -> bool:
@@ -317,9 +315,6 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
317
315
  )
318
316
 
319
317
  def inverse(self) -> "FunctionDebugInfoInterface":
320
- if self.override_debug_info is not None:
321
- self.override_debug_info = self.override_debug_info.inverse()
322
- return self
323
318
  inverse_generated_function = (
324
319
  self.generated_function.model_copy(
325
320
  update=dict(registers=self._inverse_registers)
@@ -65,7 +65,6 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
65
65
  transpiled_circuit: TranspiledCircuitData | None = pydantic.Field(default=None)
66
66
  creation_time: str = pydantic.Field(default_factory=_get_formatted_utc_current_time)
67
67
  synthesis_duration: SynthesisStepDurations | None = pydantic.Field(default=None)
68
- debug_info: list[FunctionDebugInfoInterface] | None = pydantic.Field(default=None)
69
68
  compressed_debug_info: bytes | None = pydantic.Field(default=None)
70
69
  program_id: str = pydantic.Field(default_factory=get_uuid_as_str)
71
70
  execution_primitives_input: PrimitivesInput | None = pydantic.Field(default=None)
@@ -169,9 +168,6 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
169
168
  )
170
169
 
171
170
  def get_debug_info(self) -> list[FunctionDebugInfoInterface] | None:
172
- # Support legacy uncompressed debug info
173
- if self.debug_info is not None:
174
- return self.debug_info
175
171
  if self.compressed_debug_info is None:
176
172
  return None
177
173
  decompressed_debug_info_dict_list = decompress(self.compressed_debug_info)
@@ -64,6 +64,9 @@ DEFAULT_BASIS_GATES: BasisGates = SINGLE_QUBIT_GATES | BASIC_TWO_QUBIT_GATES
64
64
  ALL_GATES: BasisGates = (
65
65
  SINGLE_QUBIT_GATES | TWO_QUBIT_GATES | THREE_QUBIT_GATES | NON_UNITARY_GATES
66
66
  )
67
+ ALL_NON_3_QBIT_GATES: BasisGates = (
68
+ SINGLE_QUBIT_GATES | TWO_QUBIT_GATES | NON_UNITARY_GATES
69
+ )
67
70
 
68
71
  ROUTING_TWO_QUBIT_BASIS_GATES: BasisGates = frozenset(
69
72
  ("cx", "ecr", "rzx", "ryy", "rxx", "rzz", "cy", "cz", "cp", "swap")
@@ -20,16 +20,7 @@ class Pauli(IntEnum):
20
20
  Z = 3
21
21
 
22
22
 
23
- class QSVMFeatureMapEntanglement(IntEnum):
24
- FULL = 0
25
- LINEAR = 1
26
- CIRCULAR = 2
27
- SCA = 3
28
- PAIRWISE = 4
29
-
30
-
31
23
  __all__ = [
32
24
  "Optimizer",
33
25
  "Pauli",
34
- "QSVMFeatureMapEntanglement",
35
26
  ]
@@ -1 +1 @@
1
- INTERFACE_VERSION = "13"
1
+ INTERFACE_VERSION = "14"
@@ -14,3 +14,7 @@ class Block(QuantumOperation):
14
14
  statements: "StatementBlock"
15
15
 
16
16
  label: str | None = pydantic.Field(default=None)
17
+
18
+ @property
19
+ def blocks(self) -> dict[str, "StatementBlock"]:
20
+ return {"statements": self.statements}
@@ -31,6 +31,10 @@ class ClassicalIf(QuantumOperation):
31
31
  def expressions(self) -> list[Expression]:
32
32
  return [self.condition]
33
33
 
34
+ @property
35
+ def blocks(self) -> dict[str, "StatementBlock"]:
36
+ return {"then": self.then, "else_": self.else_}
37
+
34
38
  @property
35
39
  def wiring_inputs(self) -> Mapping[str, HandleBinding]:
36
40
  return functools.reduce(
@@ -47,3 +47,10 @@ class Control(QuantumExpressionOperation):
47
47
 
48
48
  def _as_back_ref(self: ASTNodeType) -> ASTNodeType:
49
49
  return reset_lists(self, ["body", "else_block"])
50
+
51
+ @property
52
+ def blocks(self) -> dict[str, "StatementBlock"]:
53
+ blocks = {"body": self.body}
54
+ if self.else_block is not None:
55
+ blocks["else_block"] = self.else_block
56
+ return blocks
@@ -14,3 +14,7 @@ class Invert(QuantumOperation):
14
14
 
15
15
  def _as_back_ref(self: ASTNodeType) -> ASTNodeType:
16
16
  return reset_lists(self, ["body"])
17
+
18
+ @property
19
+ def blocks(self) -> dict[str, "StatementBlock"]:
20
+ return {"body": self.body}
@@ -1,5 +1,12 @@
1
+ from collections.abc import Collection
2
+
3
+ from pydantic import BaseModel
4
+
1
5
  from classiq.interface.debug_info.debug_info import DebugInfoCollection
2
- from classiq.interface.generator.visitor import Transformer, Visitor
6
+ from classiq.interface.generator.visitor import RetType, Transformer, Visitor
7
+ from classiq.interface.model.model import Model
8
+ from classiq.interface.model.native_function_definition import NativeFunctionDefinition
9
+ from classiq.interface.model.quantum_statement import QuantumStatement
3
10
 
4
11
 
5
12
  class ModelVisitor(Visitor):
@@ -7,8 +14,40 @@ class ModelVisitor(Visitor):
7
14
  return
8
15
 
9
16
 
17
+ class ModelStatementsVisitor(ModelVisitor):
18
+ def visit_BaseModel(self, node: BaseModel) -> RetType | None:
19
+ if isinstance(node, Model):
20
+ return self.visit(node.functions)
21
+ if isinstance(node, NativeFunctionDefinition):
22
+ return self.visit(node.body)
23
+ if isinstance(node, QuantumStatement):
24
+ for block in node.blocks.values():
25
+ self.visit(block)
26
+ return None
27
+ return super().visit_BaseModel(node)
28
+
29
+
10
30
  class ModelTransformer(Transformer):
11
31
  def visit_DebugInfoCollection(
12
32
  self, debug_info: DebugInfoCollection
13
33
  ) -> DebugInfoCollection:
14
34
  return debug_info
35
+
36
+
37
+ class ModelStatementsTransformer(ModelTransformer):
38
+ def visit_BaseModel(
39
+ self, node: BaseModel, fields_to_skip: Collection[str] | None = None
40
+ ) -> RetType:
41
+ if isinstance(node, Model):
42
+ new_functions = self.visit(node.functions)
43
+ return node.model_copy(update=dict(functions=new_functions))
44
+ if isinstance(node, NativeFunctionDefinition):
45
+ new_body = self.visit(node.body)
46
+ return node.model_copy(update=dict(body=new_body))
47
+ if isinstance(node, QuantumStatement):
48
+ new_blocks = {
49
+ block_name: self.visit(block)
50
+ for block_name, block in node.blocks.items()
51
+ }
52
+ return node.model_copy(update=new_blocks)
53
+ return super().visit_BaseModel(node, fields_to_skip)
@@ -20,3 +20,7 @@ class Power(QuantumOperation):
20
20
  @property
21
21
  def expressions(self) -> list[Expression]:
22
22
  return [self.power]
23
+
24
+ @property
25
+ def blocks(self) -> dict[str, "StatementBlock"]:
26
+ return {"body": self.body}