classiq 0.73.0__py3-none-any.whl → 0.75.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.
Files changed (49) hide show
  1. classiq/_internals/client.py +9 -10
  2. classiq/analyzer/show_interactive_hack.py +1 -1
  3. classiq/applications/qnn/qlayer.py +9 -0
  4. classiq/interface/_version.py +1 -1
  5. classiq/interface/ast_node.py +5 -2
  6. classiq/interface/compression_utils.py +31 -0
  7. classiq/interface/debug_info/debug_info.py +2 -11
  8. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +5 -5
  9. classiq/interface/generator/expressions/proxies/classical/utils.py +2 -2
  10. classiq/interface/generator/functions/classical_type.py +30 -0
  11. classiq/interface/generator/functions/type_name.py +25 -3
  12. classiq/interface/generator/generated_circuit_data.py +11 -25
  13. classiq/interface/generator/quantum_program.py +14 -0
  14. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +10 -13
  15. classiq/interface/helpers/versioned_model.py +12 -0
  16. classiq/interface/ide/visual_model.py +4 -2
  17. classiq/interface/interface_version.py +1 -1
  18. classiq/interface/model/handle_binding.py +12 -0
  19. classiq/interface/model/quantum_lambda_function.py +14 -0
  20. classiq/interface/model/statement_block.py +9 -1
  21. classiq/interface/model/within_apply_operation.py +12 -0
  22. classiq/model_expansions/atomic_expression_functions_defs.py +24 -8
  23. classiq/model_expansions/capturing/captured_vars.py +28 -6
  24. classiq/model_expansions/closure.py +13 -0
  25. classiq/model_expansions/evaluators/argument_types.py +6 -5
  26. classiq/model_expansions/evaluators/type_type_match.py +2 -1
  27. classiq/model_expansions/generative_functions.py +14 -8
  28. classiq/model_expansions/interpreters/base_interpreter.py +10 -13
  29. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +21 -0
  30. classiq/model_expansions/interpreters/generative_interpreter.py +13 -5
  31. classiq/model_expansions/quantum_operations/allocate.py +22 -11
  32. classiq/model_expansions/quantum_operations/assignment_result_processor.py +2 -0
  33. classiq/model_expansions/quantum_operations/call_emitter.py +5 -10
  34. classiq/model_expansions/quantum_operations/emitter.py +1 -5
  35. classiq/model_expansions/quantum_operations/expression_evaluator.py +1 -0
  36. classiq/model_expansions/quantum_operations/handle_evaluator.py +1 -0
  37. classiq/model_expansions/transformers/model_renamer.py +3 -1
  38. classiq/model_expansions/visitors/symbolic_param_inference.py +197 -0
  39. classiq/open_library/functions/__init__.py +2 -0
  40. classiq/open_library/functions/amplitude_amplification.py +5 -3
  41. classiq/open_library/functions/state_preparation.py +95 -2
  42. classiq/qmod/model_state_container.py +11 -8
  43. classiq/qmod/qmod_variable.py +23 -1
  44. classiq/qmod/quantum_function.py +1 -9
  45. classiq/qmod/symbolic_expr.py +8 -2
  46. classiq/qmod/write_qmod.py +5 -1
  47. {classiq-0.73.0.dist-info → classiq-0.75.0.dist-info}/METADATA +2 -1
  48. {classiq-0.73.0.dist-info → classiq-0.75.0.dist-info}/RECORD +49 -47
  49. {classiq-0.73.0.dist-info → classiq-0.75.0.dist-info}/WHEEL +1 -1
@@ -75,25 +75,23 @@ def _get_user_agent_header() -> Headers:
75
75
  python_version = (
76
76
  f"python({_get_python_execution_environment()})/{platform.python_version()}"
77
77
  )
78
-
79
- sdk_env_value = os.getenv("SDK_ENV", "Default")
80
78
  os_platform = f"{os.name}/{platform.platform()}"
81
79
  frontend_version = f"{_FRONTEND_VARIANT}/{_VERSION}"
82
80
  interface_version = f"{_INTERFACE_VARIANT}/{_VERSION}"
83
- sdk_env = f"{_SDK_ENV}/{sdk_env_value}"
81
+
84
82
  return {
85
83
  "User-Agent": _USERAGENT_SEPARATOR.join(
86
- (
87
- python_version,
88
- os_platform,
89
- frontend_version,
90
- interface_version,
91
- sdk_env,
92
- )
84
+ (python_version, os_platform, frontend_version, interface_version)
93
85
  )
94
86
  }
95
87
 
96
88
 
89
+ @functools.lru_cache
90
+ def _get_sdk_env_header() -> Headers:
91
+ sdk_env_value = os.getenv("SDK_ENV", "Default")
92
+ return {_SDK_ENV: sdk_env_value}
93
+
94
+
97
95
  Ret = TypeVar("Ret")
98
96
  P = ParamSpec("P")
99
97
 
@@ -327,6 +325,7 @@ class Client:
327
325
  if self._session_id is not None:
328
326
  headers[self._SESSION_HEADER] = self._session_id
329
327
  headers.update(_get_user_agent_header())
328
+ headers.update(_get_sdk_env_header())
330
329
  return headers
331
330
 
332
331
  async def update_expired_access_token(self) -> None:
@@ -24,7 +24,7 @@ async def handle_remote_app(circuit: QuantumProgram, display_url: bool = True) -
24
24
  )
25
25
 
26
26
  if display_url:
27
- print(f"Opening: {app_url}") # noqa: T201
27
+ print(f"Quantum program link: {app_url}") # noqa: T201
28
28
 
29
29
  webbrowser.open_new_tab(app_url)
30
30
 
@@ -1,5 +1,6 @@
1
1
  import functools
2
2
  import inspect
3
+ import os
3
4
  import typing
4
5
  from typing import Any, Callable, Optional, Union, overload
5
6
 
@@ -247,6 +248,14 @@ class QLayer(nn.Module):
247
248
  def _make_execute(
248
249
  self, quantum_program: SerializedQuantumProgram
249
250
  ) -> ExecuteFunction:
251
+ if os.environ.get("SDK_ENV") == "Studio":
252
+ try:
253
+ import classiq_studio_simulation
254
+
255
+ return classiq_studio_simulation.make_execute_qnn(quantum_program)
256
+ except ImportError:
257
+ pass
258
+
250
259
  session = ExecutionSession(quantum_program)
251
260
  self._session = session
252
261
 
@@ -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.73.0'
6
+ SEMVER_VERSION = '0.75.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -1,4 +1,4 @@
1
- from typing import Optional, TypeVar
1
+ from typing import Any, Optional, TypeVar
2
2
  from uuid import UUID
3
3
 
4
4
  import pydantic
@@ -23,7 +23,10 @@ class ASTNode(HashablePydanticBaseModel):
23
23
  def reset_lists(
24
24
  ast_node: ASTNodeType, statement_block_fields: list[str]
25
25
  ) -> ASTNodeType:
26
- return ast_node.model_copy(update={field: [] for field in statement_block_fields})
26
+ update: dict[str, Any] = {field: [] for field in statement_block_fields}
27
+ if hasattr(ast_node, "uuid"):
28
+ update["uuid"] = ast_node.uuid
29
+ return ast_node.model_copy(update=update)
27
30
 
28
31
 
29
32
  class HashableASTNode(ASTNode, HashablePydanticBaseModel):
@@ -0,0 +1,31 @@
1
+ import base64
2
+ from collections.abc import Sequence
3
+ from typing import Union
4
+
5
+ import zstandard as zstd
6
+ from pydantic import BaseModel
7
+ from pydantic_core import from_json, to_json
8
+
9
+
10
+ def compress_pydantic(obj: Union[BaseModel, Sequence[BaseModel]]) -> bytes:
11
+ json_data = to_json(obj)
12
+ compressed_data = _compress(json_data, level=22) # max compression level
13
+ return compressed_data
14
+
15
+
16
+ def decompress(compressed_data: bytes) -> Union[dict, list[dict]]:
17
+ decompressed_data = _decompress(compressed_data)
18
+ return from_json(decompressed_data)
19
+
20
+
21
+ def _compress(data: bytes, level: int) -> bytes:
22
+ compressor = zstd.ZstdCompressor(level=level)
23
+ compressed_data = compressor.compress(data)
24
+ b64_compressed_data = base64.b64encode(compressed_data)
25
+ return b64_compressed_data
26
+
27
+
28
+ def _decompress(data: bytes) -> bytes:
29
+ compressed_data = base64.b64decode(data)
30
+ decompressor = zstd.ZstdDecompressor()
31
+ return decompressor.decompress(compressed_data)
@@ -7,7 +7,6 @@ from pydantic import BaseModel, Field
7
7
  from classiq.interface.debug_info import back_ref_util
8
8
  from classiq.interface.generator.generated_circuit_data import (
9
9
  FunctionDebugInfoInterface,
10
- OperationLevel,
11
10
  StatementType,
12
11
  )
13
12
  from classiq.interface.model.block import Block
@@ -18,21 +17,15 @@ ParameterValue = Union[float, int, str, None]
18
17
 
19
18
  class FunctionDebugInfo(BaseModel):
20
19
  name: str
21
- level: OperationLevel = Field(default=OperationLevel.UNKNOWN)
22
20
  statement_type: Union[StatementType, None] = None
23
- is_allocate_or_free: bool = Field(default=False)
24
21
  is_inverse: bool = Field(default=False)
25
22
  release_by_inverse: bool = Field(default=False)
26
23
  port_to_passed_variable_map: dict[str, str] = Field(default_factory=dict)
27
24
  node: Optional[ConcreteQuantumStatement] = None
28
25
 
29
26
  @property
30
- def is_allocate_or_free_(self) -> bool:
31
- return (
32
- back_ref_util.is_allocate_or_free(self.node)
33
- if self.node is not None
34
- else self.is_allocate_or_free
35
- )
27
+ def is_allocate_or_free(self) -> bool:
28
+ return back_ref_util.is_allocate_or_free(self.node) if self.node else False
36
29
 
37
30
  def update_map_from_port_mapping(self, port_mapping: Mapping[str, str]) -> None:
38
31
  new_port_to_passed_variable_map = self.port_to_passed_variable_map.copy()
@@ -103,7 +96,5 @@ def new_function_debug_info_by_node(
103
96
  ) -> FunctionDebugInfo:
104
97
  return FunctionDebugInfo(
105
98
  name="",
106
- parameters=dict(),
107
- level=OperationLevel.QMOD_STATEMENT,
108
99
  node=node._as_back_ref(),
109
100
  )
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Union
3
3
 
4
4
  from sympy import Integer
5
5
 
6
- from classiq.interface.exceptions import ClassiqValueError
6
+ from classiq.interface.exceptions import ClassiqIndexError
7
7
  from classiq.interface.generator.expressions.expression import Expression
8
8
  from classiq.interface.generator.expressions.non_symbolic_expr import NonSymbolicExpr
9
9
  from classiq.interface.generator.expressions.proxies.classical.classical_proxy import (
@@ -51,9 +51,9 @@ class ClassicalArrayProxy(NonSymbolicExpr, ClassicalProxy):
51
51
  start = int(slice_.start)
52
52
  stop = int(slice_.stop)
53
53
  if start >= stop:
54
- raise ClassiqValueError("Array slice has non-positive length")
54
+ raise ClassiqIndexError("Array slice has non-positive length")
55
55
  if start < 0 or stop > self._length:
56
- raise ClassiqValueError("Array slice is out of bounds")
56
+ raise ClassiqIndexError("Array slice is out of bounds")
57
57
  return ClassicalArrayProxy(
58
58
  SlicedHandleBinding(
59
59
  base_handle=self.handle,
@@ -67,11 +67,11 @@ class ClassicalArrayProxy(NonSymbolicExpr, ClassicalProxy):
67
67
  def _get_subscript(self, index_: Union[int, Integer]) -> ClassicalProxy:
68
68
  index = int(index_)
69
69
  if index < 0:
70
- raise ClassiqValueError(
70
+ raise ClassiqIndexError(
71
71
  "Array index is out of bounds (negative indices are not supported)"
72
72
  )
73
73
  if index >= self._length:
74
- raise ClassiqValueError("Array index is out of bounds")
74
+ raise ClassiqIndexError("Array index is out of bounds")
75
75
  return self._element_type.get_classical_proxy(
76
76
  SubscriptHandleBinding(
77
77
  base_handle=self._handle, index=Expression(expr=str(index_))
@@ -15,7 +15,7 @@ from classiq.interface.generator.functions.classical_type import (
15
15
  ClassicalArray,
16
16
  ClassicalType,
17
17
  )
18
- from classiq.interface.generator.functions.type_name import TypeName
18
+ from classiq.interface.generator.functions.type_name import Struct
19
19
 
20
20
 
21
21
  def get_proxy_type(proxy: ClassicalProxy) -> ClassicalType:
@@ -26,7 +26,7 @@ def get_proxy_type(proxy: ClassicalProxy) -> ClassicalType:
26
26
  element_type=proxy._element_type, size=proxy.length
27
27
  )
28
28
  elif isinstance(proxy, ClassicalStructProxy):
29
- classical_type = TypeName(name=proxy._decl.name)
29
+ classical_type = Struct(name=proxy._decl.name)
30
30
  else:
31
31
  raise ClassiqInternalExpansionError(
32
32
  f"Unrecognized classical proxy {type(proxy).__name__}"
@@ -5,6 +5,7 @@ from pydantic import ConfigDict, PrivateAttr
5
5
  from typing_extensions import Self
6
6
 
7
7
  from classiq.interface.ast_node import HashableASTNode
8
+ from classiq.interface.generator.expressions.expression import Expression
8
9
  from classiq.interface.generator.expressions.proxies.classical.classical_array_proxy import (
9
10
  ClassicalArrayProxy,
10
11
  )
@@ -39,9 +40,22 @@ class ClassicalType(HashableASTNode):
39
40
  def is_generative(self) -> bool:
40
41
  return self._is_generative
41
42
 
43
+ @property
44
+ def is_purely_declarative(self) -> bool:
45
+ return not self._is_generative
46
+
42
47
  def get_classical_proxy(self, handle: HandleBinding) -> ClassicalProxy:
43
48
  return ClassicalScalarProxy(handle, self)
44
49
 
50
+ @property
51
+ def expressions(self) -> list[Expression]:
52
+ return []
53
+
54
+ def clear_flags(self) -> Self:
55
+ res = self.model_copy()
56
+ res._is_generative = False
57
+ return res
58
+
45
59
 
46
60
  class Integer(ClassicalType):
47
61
  kind: Literal["int"]
@@ -82,6 +96,14 @@ class ClassicalList(ClassicalType):
82
96
  def get_classical_proxy(self, handle: HandleBinding) -> ClassicalProxy:
83
97
  raise NotImplementedError
84
98
 
99
+ @property
100
+ def expressions(self) -> list[Expression]:
101
+ return self.element_type.expressions
102
+
103
+ @property
104
+ def is_purely_declarative(self) -> bool:
105
+ return super().is_purely_declarative and self.element_type.is_purely_declarative
106
+
85
107
 
86
108
  class StructMetaType(ClassicalType):
87
109
  kind: Literal["type_proxy"]
@@ -108,6 +130,14 @@ class ClassicalArray(ClassicalType):
108
130
  def get_classical_proxy(self, handle: HandleBinding) -> ClassicalProxy:
109
131
  return ClassicalArrayProxy(handle, self.element_type, self.size)
110
132
 
133
+ @property
134
+ def expressions(self) -> list[Expression]:
135
+ return self.element_type.expressions
136
+
137
+ @property
138
+ def is_purely_declarative(self) -> bool:
139
+ return super().is_purely_declarative and self.element_type.is_purely_declarative
140
+
111
141
 
112
142
  class OpaqueHandle(ClassicalType):
113
143
  pass
@@ -115,12 +115,34 @@ class TypeName(ClassicalType, QuantumType):
115
115
 
116
116
  @property
117
117
  def expressions(self) -> list[Expression]:
118
- return list(
119
- chain.from_iterable(
120
- field_type.expressions for field_type in self.fields.values()
118
+ if self.has_fields:
119
+ return list(
120
+ chain.from_iterable(
121
+ field_type.expressions for field_type in self.fields.values()
122
+ )
121
123
  )
124
+ if self.has_classical_struct_decl:
125
+ return list(
126
+ chain.from_iterable(
127
+ field_type.expressions
128
+ for field_type in self.classical_struct_decl.variables.values()
129
+ )
130
+ )
131
+ return []
132
+
133
+ @property
134
+ def is_purely_declarative(self) -> bool:
135
+ if self.is_enum:
136
+ return False
137
+ return super().is_purely_declarative and all(
138
+ field_type.is_purely_declarative
139
+ for field_type in self.classical_struct_decl.variables.values()
122
140
  )
123
141
 
142
+ @property
143
+ def is_enum(self) -> bool:
144
+ return not self.has_fields and not self.has_classical_struct_decl
145
+
124
146
 
125
147
  class Enum(TypeName):
126
148
  pass
@@ -40,6 +40,7 @@ IOQubitMapping: TypeAlias = dict[str, tuple[int, ...]]
40
40
  CLASSIQ_HIERARCHY_SEPARATOR: Literal["__"] = "__"
41
41
  QASM_SEPARATOR = "_"
42
42
  SPLIT_MARKER: str = "part"
43
+ ARITH_ENGINE_PREFIX = "arith_eng__"
43
44
  PART_SUFFIX_REGEX = re.compile(
44
45
  rf".+{QASM_SEPARATOR}{SPLIT_MARKER}{QASM_SEPARATOR}(\d+)$"
45
46
  )
@@ -177,10 +178,8 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
177
178
  absolute_qubits: Optional[tuple[int, ...]] = Field(default=None)
178
179
  is_basis_gate: Optional[bool] = Field(default=None)
179
180
  is_inverse: bool = Field(default=False)
180
- is_allocate_or_free: bool = Field(default=False)
181
181
  is_unitary: bool = Field(default=True, exclude=True)
182
182
  uuid: Optional[UUID] = Field(default=None, exclude=True)
183
- level: OperationLevel = Field(default=OperationLevel.UNKNOWN)
184
183
  port_to_passed_variable_map: dict[str, str] = Field(default={})
185
184
  release_by_inverse: bool = Field(default=False)
186
185
  back_refs: StatementBlock = Field(default_factory=list)
@@ -190,16 +189,8 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
190
189
  override_debug_info: Optional["FunctionDebugInfoInterface"] = None
191
190
 
192
191
  @property
193
- def is_allocate_or_free_(self) -> bool:
194
- """
195
- temporary measure to handle the fact that in the current release we do not have
196
- the backref, and therefore fallback to the old field of is_allocate_or_free
197
- """
198
- return (
199
- is_allocate_or_free_by_backref(self.back_refs)
200
- if bool(self.back_refs)
201
- else self.is_allocate_or_free
202
- )
192
+ def is_allocate_or_free(self) -> bool:
193
+ return is_allocate_or_free_by_backref(self.back_refs)
203
194
 
204
195
  @property
205
196
  def name(self) -> str:
@@ -215,10 +206,8 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
215
206
  if isinstance(back_ref, QuantumFunctionCall):
216
207
  name = generate_original_function_name(back_ref.func_name)
217
208
  if part_match := PART_SUFFIX_REGEX.match(generated_name):
218
- suffix = f" [{part_match.group(1)}]"
219
- else:
220
- suffix = ""
221
- return f"{name}{suffix}"
209
+ name += f" [{part_match.group(1)}]"
210
+ return name.removeprefix(ARITH_ENGINE_PREFIX)
222
211
 
223
212
  statement_kind: str = back_ref.kind
224
213
  if isinstance(back_ref, ArithmeticOperation):
@@ -230,19 +219,16 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
230
219
  return self.back_refs[0] if self.back_refs else None
231
220
 
232
221
  @property
233
- def level_(self) -> OperationLevel:
222
+ def level(self) -> OperationLevel:
234
223
  # Temp fix for currently "supported" statements
235
224
  if self.name in {StatementType.CONTROL, StatementType.POWER}:
236
225
  return OperationLevel.QMOD_STATEMENT
237
226
 
238
- back_ref = self.first_back_ref
239
- if back_ref is None:
240
- # This is the expected behavior for the case where there's not back ref.
241
- # We will use it once we can rely on the existence of the back ref in
242
- # all expected cases (non-engine calls)
243
- # return OperationLevel.ENGINE_FUNCTION_CALL
244
- return self.level
245
- if isinstance(back_ref, QuantumFunctionCall):
227
+ if self.first_back_ref is None:
228
+ # we use ENGINE_FUNCTION_CALL in case where there's not back ref
229
+ return OperationLevel.ENGINE_FUNCTION_CALL
230
+
231
+ if isinstance(self.first_back_ref, QuantumFunctionCall):
246
232
  return OperationLevel.QMOD_FUNCTION_CALL
247
233
  return OperationLevel.QMOD_STATEMENT
248
234
 
@@ -8,6 +8,7 @@ from pydantic import model_validator
8
8
  from pydantic_core.core_schema import ValidationInfo
9
9
  from typing_extensions import TypeAlias
10
10
 
11
+ from classiq.interface.compression_utils import decompress
11
12
  from classiq.interface.exceptions import (
12
13
  ClassiqMissingOutputFormatError,
13
14
  ClassiqStateInitializationError,
@@ -72,6 +73,7 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
72
73
  debug_info: Optional[list[FunctionDebugInfoInterface]] = pydantic.Field(
73
74
  default=None
74
75
  )
76
+ compressed_debug_info: Optional[bytes] = pydantic.Field(default=None)
75
77
  program_id: str = pydantic.Field(default_factory=get_uuid_as_str)
76
78
  execution_primitives_input: Optional[PrimitivesInput] = pydantic.Field(default=None)
77
79
 
@@ -198,3 +200,15 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
198
200
  if self.transpiled_circuit and self._can_use_transpiled_code
199
201
  else self
200
202
  )
203
+
204
+ def get_debug_info(self) -> Optional[list[FunctionDebugInfoInterface]]:
205
+ # Support legacy uncompressed debug info
206
+ if self.debug_info is not None:
207
+ return self.debug_info
208
+ if self.compressed_debug_info is None:
209
+ return None
210
+ decompressed_debug_info_dict_list = decompress(self.compressed_debug_info)
211
+ return [
212
+ FunctionDebugInfoInterface.model_validate(debug_info_dict)
213
+ for debug_info_dict in decompressed_debug_info_dict_list
214
+ ]
@@ -1,10 +1,10 @@
1
+ from itertools import chain
1
2
  from typing import Optional
2
3
 
3
4
  import pydantic
4
5
  import sympy
5
6
 
6
7
  from classiq.interface.backend.pydantic_backend import PydanticExecutionParameter
7
- from classiq.interface.exceptions import ClassiqValueError
8
8
  from classiq.interface.generator.parameters import ParameterType
9
9
 
10
10
 
@@ -12,15 +12,10 @@ class FunctionExecutionData(pydantic.BaseModel):
12
12
  power_parameter: Optional[ParameterType] = pydantic.Field(default=None)
13
13
 
14
14
  @property
15
- def power_var(self) -> Optional[str]:
15
+ def power_vars(self) -> Optional[list[str]]:
16
16
  if self.power_parameter is None:
17
17
  return None
18
- power_vars = sympy.sympify(self.power_parameter).free_symbols
19
- if len(power_vars) != 1:
20
- raise ClassiqValueError(
21
- f"Power parameter expression: {self.power_parameter} must contain exactly one variable"
22
- )
23
- return str(list(power_vars)[0])
18
+ return list(map(str, sympy.sympify(self.power_parameter).free_symbols))
24
19
 
25
20
 
26
21
  class ExecutionData(pydantic.BaseModel):
@@ -32,8 +27,10 @@ class ExecutionData(pydantic.BaseModel):
32
27
  def execution_parameters(
33
28
  self,
34
29
  ) -> set[PydanticExecutionParameter]:
35
- return {
36
- function_execution_data.power_var
37
- for function_execution_data in self.function_execution.values()
38
- if function_execution_data.power_var is not None
39
- }
30
+ return set(
31
+ chain.from_iterable(
32
+ function_execution_data.power_vars
33
+ for function_execution_data in self.function_execution.values()
34
+ if function_execution_data.power_vars is not None
35
+ )
36
+ )
@@ -1,7 +1,19 @@
1
+ from typing import Any
2
+
1
3
  import pydantic
2
4
 
3
5
  from classiq.interface._version import VERSION
6
+ from classiq.interface.interface_version import INTERFACE_VERSION
4
7
 
5
8
 
6
9
  class VersionedModel(pydantic.BaseModel):
7
10
  version: str = pydantic.Field(default=VERSION)
11
+ interface_version: str = pydantic.Field(default="0")
12
+
13
+ @pydantic.model_validator(mode="before")
14
+ @classmethod
15
+ def set_interface_version(cls, values: dict[str, Any]) -> dict[str, Any]:
16
+ # We "override" the default value mechanism so that the schema does not depend on the version
17
+ if "interface_version" not in values:
18
+ values["interface_version"] = INTERFACE_VERSION
19
+ return values
@@ -1,4 +1,5 @@
1
1
  from collections import Counter
2
+ from functools import cached_property
2
3
  from typing import Any, Optional
3
4
 
4
5
  import pydantic
@@ -58,11 +59,11 @@ class OperationLinks(pydantic.BaseModel):
58
59
  inputs: list[OperationLink]
59
60
  outputs: list[OperationLink]
60
61
 
61
- @property
62
+ @cached_property
62
63
  def input_width(self) -> int:
63
64
  return sum(len(link.qubits) for link in self.inputs)
64
65
 
65
- @property
66
+ @cached_property
66
67
  def output_width(self) -> int:
67
68
  return sum(len(link.qubits) for link in self.outputs)
68
69
 
@@ -122,6 +123,7 @@ class Operation(pydantic.BaseModel):
122
123
  default=AtomicGate.UNKNOWN, description="Gate type"
123
124
  )
124
125
  is_daggered: bool = pydantic.Field(default=False)
126
+ expanded: bool = pydantic.Field(default=False)
125
127
 
126
128
 
127
129
  class ProgramVisualModel(VersionedModel):
@@ -1 +1 @@
1
- INTERFACE_VERSION = "9"
1
+ INTERFACE_VERSION = "10"
@@ -67,6 +67,9 @@ class HandleBinding(ASTNode):
67
67
  def is_constant(self) -> bool:
68
68
  return True
69
69
 
70
+ def expressions(self) -> list[Expression]:
71
+ return []
72
+
70
73
 
71
74
  class NestedHandleBinding(HandleBinding):
72
75
  base_handle: "ConcreteHandleBinding"
@@ -111,6 +114,9 @@ class NestedHandleBinding(HandleBinding):
111
114
  def is_constant(self) -> bool:
112
115
  return self.base_handle.is_constant()
113
116
 
117
+ def expressions(self) -> list[Expression]:
118
+ return self.base_handle.expressions()
119
+
114
120
 
115
121
  class SubscriptHandleBinding(NestedHandleBinding):
116
122
  index: Expression
@@ -191,6 +197,9 @@ class SubscriptHandleBinding(NestedHandleBinding):
191
197
  and self.index.is_constant()
192
198
  )
193
199
 
200
+ def expressions(self) -> list[Expression]:
201
+ return super().expressions() + [self.index]
202
+
194
203
 
195
204
  class SlicedHandleBinding(NestedHandleBinding):
196
205
  start: Expression
@@ -305,6 +314,9 @@ class SlicedHandleBinding(NestedHandleBinding):
305
314
  and self.end.is_constant()
306
315
  )
307
316
 
317
+ def expressions(self) -> list[Expression]:
318
+ return super().expressions() + [self.start, self.end]
319
+
308
320
 
309
321
  class FieldHandleBinding(NestedHandleBinding):
310
322
  field: str
@@ -52,6 +52,20 @@ class QuantumLambdaFunction(ASTNode):
52
52
  def set_op_decl(self, fd: AnonQuantumOperandDeclaration) -> None:
53
53
  self._func_decl = fd
54
54
 
55
+ @property
56
+ def named_func_decl(self) -> AnonQuantumOperandDeclaration:
57
+ named_params = [
58
+ param.rename(rename)
59
+ for param, rename in zip(
60
+ self.func_decl.positional_arg_declarations,
61
+ self.pos_rename_params,
62
+ strict=False, # strict=False enables lambda keyword args
63
+ )
64
+ ]
65
+ return self.func_decl.model_copy(
66
+ update={"positional_arg_declarations": named_params}
67
+ )
68
+
55
69
 
56
70
  class OperandIdentifier(ASTNode):
57
71
  name: str
@@ -24,7 +24,12 @@ from classiq.interface.model.repeat import Repeat
24
24
  from classiq.interface.model.variable_declaration_statement import (
25
25
  VariableDeclarationStatement,
26
26
  )
27
- from classiq.interface.model.within_apply_operation import WithinApply
27
+ from classiq.interface.model.within_apply_operation import (
28
+ Action,
29
+ Compute,
30
+ Uncompute,
31
+ WithinApply,
32
+ )
28
33
 
29
34
  ConcreteQuantumStatement = Annotated[
30
35
  Union[
@@ -43,6 +48,9 @@ ConcreteQuantumStatement = Annotated[
43
48
  WithinApply,
44
49
  PhaseOperation,
45
50
  Block,
51
+ Compute,
52
+ Action,
53
+ Uncompute,
46
54
  ],
47
55
  Field(..., discriminator="kind"),
48
56
  ]
@@ -15,3 +15,15 @@ class WithinApply(QuantumOperation):
15
15
 
16
16
  def _as_back_ref(self: ASTNodeType) -> ASTNodeType:
17
17
  return reset_lists(self, ["compute", "action"])
18
+
19
+
20
+ class Compute(QuantumOperation):
21
+ kind: Literal["Compute"]
22
+
23
+
24
+ class Action(QuantumOperation):
25
+ kind: Literal["Action"]
26
+
27
+
28
+ class Uncompute(QuantumOperation):
29
+ kind: Literal["Uncompute"]