classiq 0.74.0__py3-none-any.whl → 0.76.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 (96) hide show
  1. classiq/_internals/api_wrapper.py +36 -0
  2. classiq/analyzer/show_interactive_hack.py +58 -2
  3. classiq/applications/chemistry/chemistry_model_constructor.py +8 -1
  4. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +2 -0
  5. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -4
  6. classiq/applications/qnn/gradients/quantum_gradient.py +3 -5
  7. classiq/applications/qnn/gradients/simple_quantum_gradient.py +2 -2
  8. classiq/applications/qnn/qlayer.py +23 -19
  9. classiq/applications/qnn/types.py +1 -4
  10. classiq/execution/__init__.py +3 -0
  11. classiq/execution/execution_session.py +3 -16
  12. classiq/execution/qnn.py +2 -2
  13. classiq/execution/user_budgets.py +38 -0
  14. classiq/executor.py +7 -19
  15. classiq/interface/_version.py +1 -1
  16. classiq/interface/debug_info/debug_info.py +18 -13
  17. classiq/interface/executor/user_budget.py +56 -0
  18. classiq/interface/generator/application_apis/finance_declarations.py +3 -0
  19. classiq/interface/generator/expressions/atomic_expression_functions.py +3 -0
  20. classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +30 -124
  21. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +46 -22
  22. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  23. classiq/interface/generator/expressions/proxies/classical/utils.py +14 -13
  24. classiq/interface/generator/expressions/proxies/quantum/qmod_qscalar_proxy.py +9 -2
  25. classiq/interface/generator/expressions/proxies/quantum/qmod_sized_proxy.py +4 -1
  26. classiq/interface/generator/expressions/sympy_supported_expressions.py +1 -0
  27. classiq/interface/generator/functions/classical_type.py +36 -1
  28. classiq/interface/generator/functions/type_name.py +32 -5
  29. classiq/interface/generator/functions/type_qualifier.py +15 -0
  30. classiq/interface/generator/generated_circuit_data.py +11 -25
  31. classiq/interface/generator/model/preferences/preferences.py +7 -0
  32. classiq/interface/generator/quantum_program.py +5 -19
  33. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +10 -13
  34. classiq/interface/helpers/backward_compatibility.py +9 -0
  35. classiq/interface/helpers/datastructures.py +6 -0
  36. classiq/interface/helpers/versioned_model.py +12 -0
  37. classiq/interface/interface_version.py +1 -1
  38. classiq/interface/model/handle_binding.py +12 -0
  39. classiq/interface/model/port_declaration.py +1 -2
  40. classiq/interface/model/quantum_lambda_function.py +2 -1
  41. classiq/interface/model/statement_block.py +9 -1
  42. classiq/interface/model/within_apply_operation.py +12 -0
  43. classiq/interface/server/routes.py +6 -0
  44. classiq/model_expansions/atomic_expression_functions_defs.py +82 -23
  45. classiq/model_expansions/capturing/captured_vars.py +2 -0
  46. classiq/model_expansions/closure.py +18 -0
  47. classiq/model_expansions/evaluators/argument_types.py +6 -5
  48. classiq/model_expansions/evaluators/classical_type_inference.py +17 -6
  49. classiq/model_expansions/evaluators/parameter_types.py +26 -13
  50. classiq/model_expansions/evaluators/type_type_match.py +2 -2
  51. classiq/model_expansions/expression_evaluator.py +1 -1
  52. classiq/model_expansions/generative_functions.py +66 -33
  53. classiq/model_expansions/interpreters/base_interpreter.py +27 -19
  54. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +26 -0
  55. classiq/model_expansions/interpreters/generative_interpreter.py +25 -1
  56. classiq/model_expansions/quantum_operations/allocate.py +27 -11
  57. classiq/model_expansions/quantum_operations/assignment_result_processor.py +220 -19
  58. classiq/model_expansions/quantum_operations/bind.py +54 -30
  59. classiq/model_expansions/quantum_operations/block_evaluator.py +42 -0
  60. classiq/model_expansions/quantum_operations/call_emitter.py +14 -12
  61. classiq/model_expansions/quantum_operations/composite_emitter.py +1 -1
  62. classiq/model_expansions/quantum_operations/declarative_call_emitter.py +23 -9
  63. classiq/model_expansions/quantum_operations/emitter.py +21 -8
  64. classiq/model_expansions/quantum_operations/expression_evaluator.py +1 -0
  65. classiq/model_expansions/quantum_operations/handle_evaluator.py +1 -0
  66. classiq/model_expansions/quantum_operations/quantum_function_call.py +4 -3
  67. classiq/model_expansions/scope.py +10 -7
  68. classiq/model_expansions/sympy_conversion/arithmetics.py +18 -0
  69. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +2 -0
  70. classiq/model_expansions/sympy_conversion/sympy_to_python.py +10 -1
  71. classiq/model_expansions/transformers/model_renamer.py +48 -8
  72. classiq/model_expansions/utils/handles_collector.py +1 -1
  73. classiq/model_expansions/visitors/symbolic_param_inference.py +197 -0
  74. classiq/model_expansions/visitors/variable_references.py +45 -9
  75. classiq/qmod/builtins/functions/allocation.py +2 -2
  76. classiq/qmod/builtins/functions/arithmetic.py +14 -12
  77. classiq/qmod/builtins/functions/standard_gates.py +23 -23
  78. classiq/qmod/declaration_inferrer.py +19 -7
  79. classiq/qmod/generative.py +9 -1
  80. classiq/qmod/native/expression_to_qmod.py +4 -0
  81. classiq/qmod/native/pretty_printer.py +8 -3
  82. classiq/qmod/pretty_print/pretty_printer.py +1 -1
  83. classiq/qmod/python_classical_type.py +4 -5
  84. classiq/qmod/qmod_constant.py +15 -7
  85. classiq/qmod/qmod_variable.py +30 -2
  86. classiq/qmod/quantum_function.py +19 -6
  87. classiq/qmod/semantics/lambdas.py +6 -2
  88. classiq/qmod/semantics/validation/main_validation.py +17 -4
  89. classiq/qmod/symbolic.py +8 -19
  90. classiq/qmod/symbolic_expr.py +34 -2
  91. classiq/qmod/write_qmod.py +5 -1
  92. classiq/synthesis.py +17 -31
  93. classiq/visualization.py +35 -0
  94. {classiq-0.74.0.dist-info → classiq-0.76.0.dist-info}/METADATA +1 -1
  95. {classiq-0.74.0.dist-info → classiq-0.76.0.dist-info}/RECORD +96 -91
  96. {classiq-0.74.0.dist-info → classiq-0.76.0.dist-info}/WHEEL +1 -1
@@ -1,7 +1,22 @@
1
1
  from classiq.interface.enum_utils import StrEnum
2
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
2
3
 
3
4
 
4
5
  class TypeQualifier(StrEnum):
5
6
  Const = "const"
6
7
  QFree = "qfree"
7
8
  Quantum = "quantum"
9
+ Inferred = "inferred"
10
+
11
+ @staticmethod
12
+ def and_(first: "TypeQualifier", second: "TypeQualifier") -> "TypeQualifier":
13
+ if second is TypeQualifier.Inferred:
14
+ raise ClassiqInternalExpansionError
15
+ if first is TypeQualifier.Quantum or second is TypeQualifier.Quantum:
16
+ return TypeQualifier.Quantum
17
+ elif first is TypeQualifier.QFree or second is TypeQualifier.QFree:
18
+ return TypeQualifier.QFree
19
+ else:
20
+ if first is not TypeQualifier.Const and second is not TypeQualifier.Const:
21
+ raise ClassiqInternalExpansionError("Unexpected type qualifiers")
22
+ return TypeQualifier.Const
@@ -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
 
@@ -102,6 +102,13 @@ class Preferences(pydantic.BaseModel, extra="forbid"):
102
102
  debug_mode (bool): If `True`, debug information is added to the
103
103
  synthesized result, potentially slowing down the synthesis. Useful for
104
104
  executing interactive algorithms. Defaults to `True`.
105
+ optimization_level (OptimizationLevel) : The optimization level used during synthesis (0-3);
106
+ determines the trade-off between synthesis speed and the quality of the results. Defaults to 3.
107
+ OptimizationLevel Options:
108
+ - NONE = 0
109
+ - LIGHT = 1
110
+ - MEDIUM = 2
111
+ - HIGH = 3
105
112
  output_format (List[QuantumFormat]): Lists the output format(s)
106
113
  for the quantum circuit. Defaults to `[QuantumFormat.QASM]`.
107
114
  `QuantumFormat` Options:
@@ -1,11 +1,9 @@
1
1
  import uuid
2
2
  from datetime import datetime, timezone
3
3
  from pathlib import Path
4
- from typing import Any, Optional, Union
4
+ from typing import Optional, Union
5
5
 
6
6
  import pydantic
7
- from pydantic import model_validator
8
- from pydantic_core.core_schema import ValidationInfo
9
7
  from typing_extensions import TypeAlias
10
8
 
11
9
  from classiq.interface.compression_utils import decompress
@@ -39,8 +37,6 @@ from classiq.interface.ide.visual_model import CircuitMetrics
39
37
  RegisterName: TypeAlias = str
40
38
  InitialConditions: TypeAlias = dict[RegisterName, int]
41
39
 
42
- OMIT_DEBUG_INFO_FLAG = "omit_debug_info"
43
-
44
40
 
45
41
  class TranspiledCircuitData(CircuitCodeInterface):
46
42
  depth: int
@@ -77,18 +73,8 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
77
73
  program_id: str = pydantic.Field(default_factory=get_uuid_as_str)
78
74
  execution_primitives_input: Optional[PrimitivesInput] = pydantic.Field(default=None)
79
75
 
80
- @model_validator(mode="before")
81
- @classmethod
82
- def remove_debug_info(
83
- cls, data: dict[str, Any], info: ValidationInfo
84
- ) -> dict[str, Any]:
85
- if (
86
- isinstance(data, dict)
87
- and info.context is not None
88
- and info.context.get(OMIT_DEBUG_INFO_FLAG, False)
89
- ):
90
- data.pop("debug_info", None)
91
- return data
76
+ def __str__(self) -> str:
77
+ return self.model_dump_json(indent=2)
92
78
 
93
79
  def _hardware_agnostic_program_code(self) -> CodeAndSyntax:
94
80
  circuit_code = self.program_circuit.get_code_by_priority()
@@ -177,7 +163,7 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
177
163
  file.write(self.model_dump_json(indent=4))
178
164
 
179
165
  @classmethod
180
- def from_qprog(cls, qprog: str) -> "QuantumProgram":
166
+ def from_qprog(cls, qprog: "QuantumProgram") -> "QuantumProgram":
181
167
  """
182
168
  Creates a `QuantumProgram` instance from a raw quantum program string.
183
169
 
@@ -187,7 +173,7 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
187
173
  Returns:
188
174
  QuantumProgram: The `QuantumProgram` instance.
189
175
  """
190
- return cls.model_validate_json(qprog)
176
+ return qprog
191
177
 
192
178
  @property
193
179
  def _can_use_transpiled_code(self) -> bool:
@@ -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
+ )
@@ -0,0 +1,9 @@
1
+ import sys
2
+ from typing import Any
3
+
4
+ if sys.version_info[0:2] >= (3, 10):
5
+ zip_strict = zip
6
+ else:
7
+
8
+ def zip_strict(*iterables: Any, strict: bool = False) -> zip:
9
+ return zip(*iterables)
@@ -17,6 +17,12 @@ class LenList(list):
17
17
  def len(self) -> int:
18
18
  return len(self)
19
19
 
20
+ def __getitem__(self, item: Any) -> Any:
21
+ res = super().__getitem__(item)
22
+ if isinstance(item, slice):
23
+ res = type(self)(res)
24
+ return res
25
+
20
26
 
21
27
  def get_sdk_compatible_python_object(obj: Any) -> Any:
22
28
  if isinstance(obj, list):
@@ -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 +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
@@ -16,8 +16,7 @@ from classiq.interface.model.parameter import Parameter
16
16
  class AnonPortDeclaration(Parameter):
17
17
  quantum_type: ConcreteQuantumType
18
18
  direction: PortDeclarationDirection
19
- # TODO remove default after BWC-breaking version
20
- type_qualifier: TypeQualifier = pydantic.Field(default=TypeQualifier.Quantum)
19
+ type_qualifier: TypeQualifier
21
20
  kind: Literal["PortDeclaration"]
22
21
 
23
22
  @pydantic.model_validator(mode="before")
@@ -5,6 +5,7 @@ import pydantic
5
5
  from classiq.interface.ast_node import ASTNode
6
6
  from classiq.interface.exceptions import ClassiqError
7
7
  from classiq.interface.generator.expressions.expression import Expression
8
+ from classiq.interface.helpers.backward_compatibility import zip_strict
8
9
  from classiq.interface.model.quantum_function_declaration import (
9
10
  AnonQuantumOperandDeclaration,
10
11
  )
@@ -56,7 +57,7 @@ class QuantumLambdaFunction(ASTNode):
56
57
  def named_func_decl(self) -> AnonQuantumOperandDeclaration:
57
58
  named_params = [
58
59
  param.rename(rename)
59
- for param, rename in zip(
60
+ for param, rename in zip_strict(
60
61
  self.func_decl.positional_arg_declarations,
61
62
  self.pos_rename_params,
62
63
  strict=False, # strict=False enables lambda keyword args
@@ -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"]
@@ -6,6 +6,8 @@ CONVERSION_PREFIX = "/conversion"
6
6
  PROVIDERS_PREFIX = "/providers"
7
7
 
8
8
  IQCC_PREFIX = PROVIDERS_PREFIX + "/iqcc"
9
+ USER_BUDGETS_PREFIX = "/user_budgets"
10
+
9
11
 
10
12
  ANALYZER_CIRCUIT_PAGE = "circuit"
11
13
  DEFAULT_IDE_FE_APP = "https://platform.classiq.io/"
@@ -50,6 +52,7 @@ TASK_TEST_SUFFIX = TASKS_SUFFIX + "/test"
50
52
  TASK_PREDICT_SUFFIX = TASKS_SUFFIX + "/predict"
51
53
  TASK_RB_SUFFIX = TASKS_SUFFIX + RB
52
54
  TASKS_GENERATE_FULL_PATH = TASKS_GENERATE_SUFFIX
55
+ TASKS_VISUAL_MODEL_FULL_PATH = ANALYZER_PREFIX + TASKS_VISUAL_MODEL_SUFFIX
53
56
 
54
57
  EXECUTION_JOBS_SUFFIX = "/jobs"
55
58
  EXECUTION_JOBS_FULL_PATH = EXECUTION_PREFIX + EXECUTION_JOBS_SUFFIX
@@ -80,3 +83,6 @@ IQCC_LIST_AUTH_METHODS_SUFFIX = "/auth_methods"
80
83
  IQCC_LIST_AUTH_METHODS_FULL_PATH = IQCC_PREFIX + IQCC_LIST_AUTH_METHODS_SUFFIX
81
84
  IQCC_LIST_AUTH_TARGETS_SUFFIX = "/auth_targets"
82
85
  IQCC_LIST_AUTH_TARGETS_FULL_PATH = IQCC_PREFIX + IQCC_LIST_AUTH_TARGETS_SUFFIX
86
+
87
+ USER_BUDGETS_SUFFIX = "/all"
88
+ USER_BUDGETS_FULL_PATH = USER_BUDGETS_PREFIX + USER_BUDGETS_SUFFIX
@@ -2,6 +2,7 @@ from collections.abc import Mapping
2
2
  from enum import Enum
3
3
  from typing import Any, Callable, Union, get_args
4
4
 
5
+ import sympy
5
6
  from sympy import Eq, Expr, Piecewise, Symbol
6
7
 
7
8
  from classiq.interface.exceptions import (
@@ -15,6 +16,9 @@ from classiq.interface.generator.expressions.expression_types import (
15
16
  from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
16
17
  AnyClassicalValue,
17
18
  )
19
+ from classiq.interface.generator.expressions.proxies.classical.classical_array_proxy import (
20
+ ClassicalArrayProxy,
21
+ )
18
22
  from classiq.interface.generator.expressions.proxies.classical.classical_proxy import (
19
23
  ClassicalProxy,
20
24
  )
@@ -53,6 +57,8 @@ from classiq.model_expansions.sympy_conversion.arithmetics import (
53
57
  BitwiseOr,
54
58
  BitwiseXor,
55
59
  LogicalXor,
60
+ LShift,
61
+ RShift,
56
62
  )
57
63
  from classiq.model_expansions.sympy_conversion.expression_to_sympy import (
58
64
  MISSING_SLICE_VALUE_PLACEHOLDER,
@@ -60,11 +66,12 @@ from classiq.model_expansions.sympy_conversion.expression_to_sympy import (
60
66
  from classiq.model_expansions.sympy_conversion.sympy_to_python import (
61
67
  sympy_to_python,
62
68
  )
63
- from classiq.model_expansions.utils.sympy_utils import (
64
- is_constant_subscript,
65
- unwrap_sympy_numeric,
69
+ from classiq.model_expansions.utils.sympy_utils import is_constant_subscript
70
+ from classiq.qmod.builtins.classical_functions import (
71
+ __all__ as qmod_classical_functions,
66
72
  )
67
73
  from classiq.qmod.model_state_container import QMODULE
74
+ from classiq.qmod.utilities import qmod_val_to_expr_str
68
75
 
69
76
 
70
77
  def qmod_val_to_python(val: ExpressionValue, qmod_type: ClassicalType) -> Any:
@@ -166,15 +173,14 @@ def get_field(
166
173
  ],
167
174
  field: str,
168
175
  ) -> ExpressionValue:
169
- if isinstance(proxy, AnyClassicalValue):
170
- return AnyClassicalValue(f"({proxy}).{field}")
176
+ if isinstance(proxy, AnyClassicalValue) or (
177
+ isinstance(proxy, Symbol)
178
+ and not isinstance(proxy, QmodSizedProxy)
179
+ and not isinstance(proxy, ClassicalProxy)
180
+ ):
181
+ return AnyClassicalValue(f"get_field({qmod_val_to_expr_str(proxy)}, '{field}')")
171
182
  if isinstance(proxy, type) and issubclass(proxy, Enum):
172
183
  return getattr(proxy, field)
173
- if isinstance(proxy, Symbol) and not isinstance(proxy, QmodSizedProxy):
174
- raise ClassiqExpansionError(
175
- f"Cannot evaluate '{proxy}.{field}': Variable {str(proxy)!r} is not "
176
- f"initialized"
177
- )
178
184
  if isinstance(proxy, list):
179
185
  if field != "len":
180
186
  raise ClassiqExpansionError(
@@ -210,9 +216,10 @@ def get_type(struct_type: Symbol) -> TypeProxy:
210
216
 
211
217
 
212
218
  def do_div(lhs: Any, rhs: Any) -> Any:
213
- lhs = unwrap_sympy_numeric(lhs)
214
- rhs = unwrap_sympy_numeric(rhs)
215
- return lhs / rhs
219
+ res = lhs / rhs
220
+ if isinstance(res, sympy.Expr):
221
+ res = res.evalf()
222
+ return res
216
223
 
217
224
 
218
225
  _EXPRESSION_TYPES = get_args(ExpressionValue)
@@ -229,7 +236,9 @@ def _is_qmod_value(val: Any) -> bool:
229
236
 
230
237
 
231
238
  def do_subscript(value: Any, index: Any) -> Any:
232
- if not isinstance(value, list) or not isinstance(index, QmodQNumProxy):
239
+ if not isinstance(value, (list, ClassicalArrayProxy)) or not isinstance(
240
+ index, QmodQNumProxy
241
+ ):
233
242
  if isinstance(index, (QmodSizedProxy, QmodStructInstance)):
234
243
  raise ClassiqExpansionError(
235
244
  f"Subscript {value}[{index}] is not supported. Supported subscripts "
@@ -244,22 +253,31 @@ def do_subscript(value: Any, index: Any) -> Any:
244
253
  and not is_constant_subscript(index)
245
254
  and _is_qmod_value(index)
246
255
  ):
247
- return AnyClassicalValue(str(value))[index]
256
+ return AnyClassicalValue(qmod_val_to_expr_str(value))[index]
248
257
  return value[index]
249
258
  if index.is_signed or index.fraction_digits > 0:
250
259
  raise ClassiqExpansionError(
251
260
  "Quantum numeric subscript must be an unsigned integer (is_signed=False, "
252
261
  "fraction_digits=0)"
253
262
  )
254
- if len(value) != 2**index.size:
263
+ if isinstance(value, ClassicalArrayProxy):
264
+ length = value.length
265
+ else:
266
+ length = len(value)
267
+ if length != 2**index.size:
255
268
  raise ClassiqExpansionError(
256
269
  f"Quantum numeric subscript size mismatch: The quantum numeric has "
257
- f"{index.size} qubits but the list size is {len(value)} != 2**{index.size}"
270
+ f"{index.size} qubits but the list size is {length} != 2**{index.size}"
271
+ )
272
+ if isinstance(value, ClassicalArrayProxy):
273
+ return AnyClassicalValue(
274
+ f"do_subscript({qmod_val_to_expr_str(value)}, {qmod_val_to_expr_str(index)})"
275
+ )
276
+ else:
277
+ return Piecewise(
278
+ *[(item, Eq(index, idx)) for idx, item in enumerate(value[:-1])],
279
+ (value[-1], True),
258
280
  )
259
- return Piecewise(
260
- *[(item, Eq(index, idx)) for idx, item in enumerate(value[:-1])],
261
- (value[-1], True),
262
- )
263
281
 
264
282
 
265
283
  def do_slice(value: Any, lower: Any, upper: Any) -> Any:
@@ -270,9 +288,29 @@ def do_slice(value: Any, lower: Any, upper: Any) -> Any:
270
288
  return do_subscript(value, slice(lower, upper))
271
289
 
272
290
 
291
+ def do_sum(val: Any) -> Any:
292
+ if isinstance(val, AnyClassicalValue):
293
+ return AnyClassicalValue(f"sum({val})")
294
+ return sum(val)
295
+
296
+
297
+ do_sum.__name__ = "sum"
298
+
299
+
300
+ def mod_inverse(a: Any, b: Any) -> Any:
301
+ if (
302
+ isinstance(a, AnyClassicalValue)
303
+ or (isinstance(a, sympy.Basic) and len(a.free_symbols) > 0)
304
+ or isinstance(b, AnyClassicalValue)
305
+ or (isinstance(b, sympy.Basic) and len(b.free_symbols) > 0)
306
+ ):
307
+ return AnyClassicalValue(f"mod_inverse({a}, {b})")
308
+ return sympy.mod_inverse(a, b)
309
+
310
+
273
311
  CORE_LIB_FUNCTIONS_LIST: list[Callable] = [
274
312
  print,
275
- sum,
313
+ do_sum,
276
314
  struct_literal,
277
315
  get_field,
278
316
  get_type,
@@ -284,8 +322,29 @@ CORE_LIB_FUNCTIONS_LIST: list[Callable] = [
284
322
  BitwiseNot,
285
323
  BitwiseOr,
286
324
  LogicalXor,
325
+ RShift,
326
+ LShift,
327
+ mod_inverse,
328
+ ]
329
+
330
+
331
+ def _symbolic_function(func: str) -> Callable:
332
+ def wrapper(*args: Any) -> AnyClassicalValue:
333
+ return AnyClassicalValue(
334
+ f"{func}({', '.join(map(qmod_val_to_expr_str, args))})"
335
+ )
336
+
337
+ wrapper.__name__ = func
338
+ return wrapper
339
+
340
+
341
+ QMOD_CLASSICAL_FUNCTIONS = [
342
+ _symbolic_function(func) for func in qmod_classical_functions
287
343
  ]
288
344
 
289
345
  ATOMIC_EXPRESSION_FUNCTIONS = {
290
- **{core_func.__name__: core_func for core_func in CORE_LIB_FUNCTIONS_LIST},
346
+ **{
347
+ core_func.__name__: core_func
348
+ for core_func in CORE_LIB_FUNCTIONS_LIST + QMOD_CLASSICAL_FUNCTIONS
349
+ },
291
350
  }
@@ -15,6 +15,7 @@ from classiq.interface.generator.functions.classical_type import ClassicalType
15
15
  from classiq.interface.generator.functions.port_declaration import (
16
16
  PortDeclarationDirection,
17
17
  )
18
+ from classiq.interface.generator.functions.type_qualifier import TypeQualifier
18
19
  from classiq.interface.model.classical_parameter_declaration import (
19
20
  ClassicalParameterDeclaration,
20
21
  )
@@ -132,6 +133,7 @@ class _CapturedHandle(_Captured):
132
133
  name=self.mangled_name,
133
134
  quantum_type=self.quantum_type,
134
135
  direction=self.direction.dump(),
136
+ type_qualifier=TypeQualifier.Inferred, # TODO https://classiq.atlassian.net/browse/CLS-1830
135
137
  )
136
138
 
137
139
  def is_same_var(self, other: "_CapturedHandle") -> bool:
@@ -8,6 +8,16 @@ from typing import Any, Optional
8
8
  from typing_extensions import Self
9
9
 
10
10
  from classiq.interface.exceptions import ClassiqInternalExpansionError
11
+ from classiq.interface.generator.expressions.proxies.classical.classical_proxy import (
12
+ ClassicalProxy,
13
+ )
14
+ from classiq.interface.generator.expressions.proxies.classical.classical_struct_proxy import (
15
+ ClassicalStructProxy,
16
+ )
17
+ from classiq.interface.generator.expressions.proxies.classical.utils import (
18
+ get_proxy_type,
19
+ )
20
+ from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
11
21
  from classiq.interface.model.quantum_function_declaration import (
12
22
  NamedParamsQuantumFunctionDeclaration,
13
23
  PositionalArg,
@@ -34,6 +44,10 @@ class Closure:
34
44
  positional_arg_declarations: Sequence[PositionalArg] = tuple()
35
45
  captured_vars: CapturedVars = field(default_factory=CapturedVars)
36
46
 
47
+ @property
48
+ def parameters_dict(self) -> dict[str, PositionalArg]:
49
+ return nameables_to_dict(self.positional_arg_declarations)
50
+
37
51
 
38
52
  @dataclass(frozen=True)
39
53
  class GenerativeClosure(Closure):
@@ -139,6 +153,10 @@ def _evaluated_arg_to_str(arg: Any) -> str:
139
153
  return _evaluated_one_operand_to_str(arg)
140
154
  if isinstance(arg, list) and arg and isinstance(arg[0], FunctionClosure):
141
155
  return _evaluated_operands_list_to_str(arg)
156
+ if isinstance(arg, ClassicalProxy):
157
+ if isinstance(arg, ClassicalStructProxy):
158
+ return repr(arg.struct_declaration)
159
+ return repr(get_proxy_type(arg))
142
160
  return evaluated_classical_param_to_str(arg)
143
161
 
144
162
 
@@ -34,9 +34,10 @@ def add_information_from_output_arguments(
34
34
  if parameter.direction != PortDeclarationDirection.Output:
35
35
  continue
36
36
 
37
- copy_type_information(
38
- parameter.quantum_type,
39
- argument_as_quantum_symbol.quantum_type,
40
- str(argument_as_quantum_symbol.handle),
41
- )
37
+ if parameter.quantum_type.is_evaluated:
38
+ copy_type_information(
39
+ parameter.quantum_type,
40
+ argument_as_quantum_symbol.quantum_type,
41
+ str(argument_as_quantum_symbol.handle),
42
+ )
42
43
  return args