classiq 0.75.0__py3-none-any.whl → 0.77.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 (101) 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 +15 -7
  4. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +11 -1
  5. classiq/applications/combinatorial_optimization/combinatorial_problem.py +8 -7
  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 +14 -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 +16 -2
  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 +45 -21
  22. classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
  23. classiq/interface/generator/expressions/proxies/classical/utils.py +12 -11
  24. classiq/interface/generator/expressions/proxies/quantum/qmod_qarray_proxy.py +6 -15
  25. classiq/interface/generator/expressions/proxies/quantum/qmod_qscalar_proxy.py +22 -6
  26. classiq/interface/generator/expressions/proxies/quantum/qmod_sized_proxy.py +9 -4
  27. classiq/interface/generator/expressions/sympy_supported_expressions.py +1 -0
  28. classiq/interface/generator/functions/classical_type.py +6 -1
  29. classiq/interface/generator/functions/type_name.py +7 -2
  30. classiq/interface/generator/functions/type_qualifier.py +15 -0
  31. classiq/interface/generator/model/preferences/preferences.py +7 -0
  32. classiq/interface/generator/quantum_program.py +5 -19
  33. classiq/interface/helpers/backward_compatibility.py +9 -0
  34. classiq/interface/helpers/datastructures.py +6 -0
  35. classiq/interface/model/handle_binding.py +8 -0
  36. classiq/interface/model/model.py +3 -6
  37. classiq/interface/model/port_declaration.py +1 -2
  38. classiq/interface/model/quantum_function_call.py +31 -1
  39. classiq/interface/model/quantum_lambda_function.py +2 -1
  40. classiq/interface/model/quantum_statement.py +14 -1
  41. classiq/interface/server/routes.py +6 -0
  42. classiq/interface/source_reference.py +7 -2
  43. classiq/model_expansions/atomic_expression_functions_defs.py +62 -19
  44. classiq/model_expansions/capturing/captured_vars.py +18 -6
  45. classiq/model_expansions/closure.py +5 -0
  46. classiq/model_expansions/evaluators/arg_type_match.py +2 -2
  47. classiq/model_expansions/evaluators/argument_types.py +3 -3
  48. classiq/model_expansions/evaluators/classical_expression.py +9 -9
  49. classiq/model_expansions/evaluators/classical_type_inference.py +17 -6
  50. classiq/model_expansions/evaluators/parameter_types.py +45 -24
  51. classiq/model_expansions/expression_evaluator.py +21 -12
  52. classiq/model_expansions/function_builder.py +45 -0
  53. classiq/model_expansions/generative_functions.py +62 -35
  54. classiq/model_expansions/interpreters/base_interpreter.py +32 -7
  55. classiq/model_expansions/interpreters/frontend_generative_interpreter.py +9 -3
  56. classiq/model_expansions/interpreters/generative_interpreter.py +17 -5
  57. classiq/model_expansions/quantum_operations/allocate.py +8 -3
  58. classiq/model_expansions/quantum_operations/assignment_result_processor.py +221 -20
  59. classiq/model_expansions/quantum_operations/bind.py +54 -30
  60. classiq/model_expansions/quantum_operations/block_evaluator.py +42 -0
  61. classiq/model_expansions/quantum_operations/call_emitter.py +35 -18
  62. classiq/model_expansions/quantum_operations/composite_emitter.py +1 -1
  63. classiq/model_expansions/quantum_operations/declarative_call_emitter.py +23 -9
  64. classiq/model_expansions/quantum_operations/emitter.py +21 -9
  65. classiq/model_expansions/quantum_operations/quantum_function_call.py +4 -3
  66. classiq/model_expansions/scope.py +63 -10
  67. classiq/model_expansions/sympy_conversion/arithmetics.py +18 -0
  68. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +2 -0
  69. classiq/model_expansions/sympy_conversion/sympy_to_python.py +10 -1
  70. classiq/model_expansions/transformers/model_renamer.py +45 -7
  71. classiq/model_expansions/utils/handles_collector.py +1 -1
  72. classiq/model_expansions/visitors/symbolic_param_inference.py +3 -3
  73. classiq/model_expansions/visitors/variable_references.py +45 -9
  74. classiq/open_library/functions/lookup_table.py +1 -1
  75. classiq/open_library/functions/state_preparation.py +1 -1
  76. classiq/qmod/builtins/functions/allocation.py +2 -2
  77. classiq/qmod/builtins/functions/arithmetic.py +14 -12
  78. classiq/qmod/builtins/functions/standard_gates.py +23 -23
  79. classiq/qmod/create_model_function.py +21 -3
  80. classiq/qmod/declaration_inferrer.py +19 -7
  81. classiq/qmod/generative.py +9 -1
  82. classiq/qmod/global_declarative_switch.py +19 -0
  83. classiq/qmod/native/expression_to_qmod.py +4 -0
  84. classiq/qmod/native/pretty_printer.py +12 -3
  85. classiq/qmod/pretty_print/pretty_printer.py +5 -1
  86. classiq/qmod/python_classical_type.py +4 -5
  87. classiq/qmod/qfunc.py +31 -23
  88. classiq/qmod/qmod_constant.py +15 -7
  89. classiq/qmod/qmod_variable.py +7 -1
  90. classiq/qmod/quantum_expandable.py +29 -1
  91. classiq/qmod/quantum_function.py +45 -25
  92. classiq/qmod/semantics/lambdas.py +6 -2
  93. classiq/qmod/semantics/validation/main_validation.py +17 -4
  94. classiq/qmod/symbolic.py +8 -19
  95. classiq/qmod/symbolic_expr.py +26 -0
  96. classiq/qmod/write_qmod.py +36 -10
  97. classiq/synthesis.py +24 -37
  98. classiq/visualization.py +35 -0
  99. {classiq-0.75.0.dist-info → classiq-0.77.0.dist-info}/METADATA +1 -1
  100. {classiq-0.75.0.dist-info → classiq-0.77.0.dist-info}/RECORD +101 -96
  101. {classiq-0.75.0.dist-info → classiq-0.77.0.dist-info}/WHEEL +0 -0
@@ -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
@@ -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:
@@ -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):
@@ -355,3 +355,11 @@ ConcreteHandleBinding = Union[
355
355
  SubscriptHandleBinding.model_rebuild()
356
356
  SlicedHandleBinding.model_rebuild()
357
357
  FieldHandleBinding.model_rebuild()
358
+
359
+
360
+ class HandlesList(ASTNode):
361
+ handles: list["GeneralHandle"]
362
+
363
+
364
+ GeneralHandle = Union[ConcreteHandleBinding, HandlesList]
365
+ HandlesList.model_rebuild()
@@ -1,6 +1,6 @@
1
1
  from collections import Counter
2
2
  from collections.abc import Mapping
3
- from typing import Any, Literal, NewType, Optional
3
+ from typing import Any, Literal, NewType
4
4
 
5
5
  import pydantic
6
6
 
@@ -9,7 +9,6 @@ from classiq.interface.debug_info.debug_info import DebugInfoCollection
9
9
  from classiq.interface.exceptions import ClassiqValueError
10
10
  from classiq.interface.executor.execution_preferences import ExecutionPreferences
11
11
  from classiq.interface.generator.constant import Constant
12
- from classiq.interface.generator.functions.concrete_types import ConcreteClassicalType
13
12
  from classiq.interface.generator.functions.port_declaration import (
14
13
  PortDeclarationDirection,
15
14
  )
@@ -103,9 +102,6 @@ class Model(VersionedModel, ASTNode):
103
102
  functions_compilation_metadata: dict[str, CompilationMetadata] = pydantic.Field(
104
103
  default_factory=dict
105
104
  )
106
- execution_parameters: Optional[dict[str, ConcreteClassicalType]] = pydantic.Field(
107
- default=None, exclude=True
108
- )
109
105
 
110
106
  @property
111
107
  def main_func(self) -> NativeFunctionDefinition:
@@ -184,11 +180,12 @@ class Model(VersionedModel, ASTNode):
184
180
  )
185
181
  return constants
186
182
 
187
- def dump_no_preferences_and_constraints(self) -> dict[str, Any]:
183
+ def dump_no_metadata(self) -> dict[str, Any]:
188
184
  return self.model_dump(
189
185
  exclude={
190
186
  "constraints",
191
187
  "execution_preferences",
192
188
  "preferences",
189
+ "functions_compilation_metadata",
193
190
  },
194
191
  )
@@ -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")
@@ -1,4 +1,5 @@
1
1
  from collections.abc import Iterable, Mapping, Sequence
2
+ from itertools import chain
2
3
  from typing import (
3
4
  Literal,
4
5
  Optional,
@@ -15,7 +16,9 @@ from classiq.interface.generator.functions.port_declaration import (
15
16
  )
16
17
  from classiq.interface.model.handle_binding import (
17
18
  ConcreteHandleBinding,
19
+ GeneralHandle,
18
20
  HandleBinding,
21
+ HandlesList,
19
22
  )
20
23
  from classiq.interface.model.port_declaration import AnonPortDeclaration
21
24
  from classiq.interface.model.quantum_function_declaration import (
@@ -31,6 +34,7 @@ ArgValue = Union[
31
34
  Expression,
32
35
  QuantumOperand,
33
36
  ConcreteHandleBinding,
37
+ HandlesList,
34
38
  ]
35
39
 
36
40
 
@@ -146,6 +150,26 @@ class QuantumFunctionCall(QuantumOperation):
146
150
  )
147
151
  ]
148
152
 
153
+ @property
154
+ def handles_with_directions(
155
+ self,
156
+ ) -> Iterable[tuple[HandleBinding, PortDeclarationDirection]]:
157
+ return [(handle, param.direction) for handle, param in self.handles_with_params]
158
+
159
+ @property
160
+ def handles_with_params(
161
+ self,
162
+ ) -> Iterable[tuple[HandleBinding, AnonPortDeclaration]]:
163
+ return [
164
+ (handle, param)
165
+ for arg, param in zip(
166
+ self.positional_args, self.func_decl.positional_arg_declarations
167
+ )
168
+ if isinstance(param, AnonPortDeclaration)
169
+ and isinstance(arg, (HandleBinding, HandlesList))
170
+ for handle in _get_handles(arg)
171
+ ]
172
+
149
173
  @property
150
174
  def params(self) -> list[Expression]:
151
175
  return [
@@ -161,7 +185,7 @@ class QuantumFunctionCall(QuantumOperation):
161
185
  return [
162
186
  param
163
187
  for param in self.positional_args
164
- if not isinstance(param, (Expression, HandleBinding))
188
+ if not isinstance(param, (Expression, HandleBinding, HandlesList))
165
189
  ]
166
190
 
167
191
  @property
@@ -224,3 +248,9 @@ class QuantumFunctionCall(QuantumOperation):
224
248
  f" for parameter {param_name}" if len(self.positional_args) > 1 else ""
225
249
  )
226
250
  return f"as an argument{param_text} of function {self.func_name!r}"
251
+
252
+
253
+ def _get_handles(var: GeneralHandle) -> Iterable[HandleBinding]:
254
+ if isinstance(var, HandleBinding):
255
+ return [var]
256
+ return chain.from_iterable(_get_handles(item) for item in var.handles)
@@ -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
@@ -1,4 +1,4 @@
1
- from collections.abc import Mapping, Sequence
1
+ from collections.abc import Iterable, Mapping, Sequence
2
2
  from dataclasses import dataclass
3
3
  from typing import Any, Callable, Optional
4
4
  from uuid import UUID, uuid4
@@ -9,6 +9,9 @@ from typing_extensions import Self
9
9
 
10
10
  from classiq.interface.ast_node import ASTNode
11
11
  from classiq.interface.generator.expressions.expression import Expression
12
+ from classiq.interface.generator.functions.port_declaration import (
13
+ PortDeclarationDirection,
14
+ )
12
15
  from classiq.interface.helpers.pydantic_model_helpers import values_with_discriminator
13
16
  from classiq.interface.model.handle_binding import (
14
17
  ConcreteHandleBinding,
@@ -90,6 +93,16 @@ class QuantumOperation(QuantumStatement):
90
93
  def readable_outputs(self) -> Sequence[HandleMetadata]:
91
94
  return [HandleMetadata(handle=handle) for handle in self.outputs]
92
95
 
96
+ @property
97
+ def handles_with_directions(
98
+ self,
99
+ ) -> Iterable[tuple[HandleBinding, PortDeclarationDirection]]:
100
+ return (
101
+ [(handle, PortDeclarationDirection.Input) for handle in self.inputs]
102
+ + [(handle, PortDeclarationDirection.Output) for handle in self.outputs]
103
+ + [(handle, PortDeclarationDirection.Inout) for handle in self.inouts]
104
+ )
105
+
93
106
  def set_generative_block(self, block_name: str, py_callable: Callable) -> None:
94
107
  self._generative_blocks[block_name] = py_callable
95
108
 
@@ -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
@@ -33,11 +33,16 @@ class SourceReference(HashablePydanticBaseModel):
33
33
  file_name: Optional[str] = pydantic.Field(default=None)
34
34
 
35
35
  def __str__(self) -> str:
36
- file_string = _prepare_file_string(self.file_name) if self.file_name else ""
36
+ return f"{self.file_string()}{self.ref_inside_file()}"
37
+
38
+ def file_string(self) -> str:
39
+ return _prepare_file_string(self.file_name) if self.file_name else ""
40
+
41
+ def ref_inside_file(self) -> str:
37
42
  start_character_string = (
38
43
  f" character {self.start_column + 1}" if self.start_column > 0 else ""
39
44
  )
40
- return f"{file_string}line {self.start_line + 1}{start_character_string}"
45
+ return f"line {self.start_line + 1}{start_character_string}"
41
46
 
42
47
 
43
48
  class SourceReferencedError(pydantic.BaseModel):
@@ -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 (
@@ -56,6 +57,8 @@ from classiq.model_expansions.sympy_conversion.arithmetics import (
56
57
  BitwiseOr,
57
58
  BitwiseXor,
58
59
  LogicalXor,
60
+ LShift,
61
+ RShift,
59
62
  )
60
63
  from classiq.model_expansions.sympy_conversion.expression_to_sympy import (
61
64
  MISSING_SLICE_VALUE_PLACEHOLDER,
@@ -63,11 +66,12 @@ from classiq.model_expansions.sympy_conversion.expression_to_sympy import (
63
66
  from classiq.model_expansions.sympy_conversion.sympy_to_python import (
64
67
  sympy_to_python,
65
68
  )
66
- from classiq.model_expansions.utils.sympy_utils import (
67
- is_constant_subscript,
68
- 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,
69
72
  )
70
73
  from classiq.qmod.model_state_container import QMODULE
74
+ from classiq.qmod.utilities import qmod_val_to_expr_str
71
75
 
72
76
 
73
77
  def qmod_val_to_python(val: ExpressionValue, qmod_type: ClassicalType) -> Any:
@@ -169,19 +173,14 @@ def get_field(
169
173
  ],
170
174
  field: str,
171
175
  ) -> ExpressionValue:
172
- if isinstance(proxy, AnyClassicalValue):
173
- return AnyClassicalValue(f"({proxy}).{field}")
174
- if isinstance(proxy, type) and issubclass(proxy, Enum):
175
- return getattr(proxy, field)
176
- if (
176
+ if isinstance(proxy, AnyClassicalValue) or (
177
177
  isinstance(proxy, Symbol)
178
178
  and not isinstance(proxy, QmodSizedProxy)
179
179
  and not isinstance(proxy, ClassicalProxy)
180
180
  ):
181
- raise ClassiqExpansionError(
182
- f"Cannot evaluate '{proxy}.{field}': Variable {str(proxy)!r} is not "
183
- f"initialized"
184
- )
181
+ return AnyClassicalValue(f"get_field({qmod_val_to_expr_str(proxy)}, '{field}')")
182
+ if isinstance(proxy, type) and issubclass(proxy, Enum):
183
+ return getattr(proxy, field)
185
184
  if isinstance(proxy, list):
186
185
  if field != "len":
187
186
  raise ClassiqExpansionError(
@@ -217,9 +216,10 @@ def get_type(struct_type: Symbol) -> TypeProxy:
217
216
 
218
217
 
219
218
  def do_div(lhs: Any, rhs: Any) -> Any:
220
- lhs = unwrap_sympy_numeric(lhs)
221
- rhs = unwrap_sympy_numeric(rhs)
222
- return lhs / rhs
219
+ res = lhs / rhs
220
+ if isinstance(res, sympy.Expr):
221
+ res = res.evalf()
222
+ return res
223
223
 
224
224
 
225
225
  _EXPRESSION_TYPES = get_args(ExpressionValue)
@@ -253,7 +253,7 @@ def do_subscript(value: Any, index: Any) -> Any:
253
253
  and not is_constant_subscript(index)
254
254
  and _is_qmod_value(index)
255
255
  ):
256
- return AnyClassicalValue(str(value))[index]
256
+ return AnyClassicalValue(qmod_val_to_expr_str(value))[index]
257
257
  return value[index]
258
258
  if index.is_signed or index.fraction_digits > 0:
259
259
  raise ClassiqExpansionError(
@@ -270,7 +270,9 @@ def do_subscript(value: Any, index: Any) -> Any:
270
270
  f"{index.size} qubits but the list size is {length} != 2**{index.size}"
271
271
  )
272
272
  if isinstance(value, ClassicalArrayProxy):
273
- return AnyClassicalValue(f"{value}[{index}]")
273
+ return AnyClassicalValue(
274
+ f"do_subscript({qmod_val_to_expr_str(value)}, {qmod_val_to_expr_str(index)})"
275
+ )
274
276
  else:
275
277
  return Piecewise(
276
278
  *[(item, Eq(index, idx)) for idx, item in enumerate(value[:-1])],
@@ -286,9 +288,29 @@ def do_slice(value: Any, lower: Any, upper: Any) -> Any:
286
288
  return do_subscript(value, slice(lower, upper))
287
289
 
288
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
+
289
311
  CORE_LIB_FUNCTIONS_LIST: list[Callable] = [
290
312
  print,
291
- sum,
313
+ do_sum,
292
314
  struct_literal,
293
315
  get_field,
294
316
  get_type,
@@ -300,8 +322,29 @@ CORE_LIB_FUNCTIONS_LIST: list[Callable] = [
300
322
  BitwiseNot,
301
323
  BitwiseOr,
302
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
303
343
  ]
304
344
 
305
345
  ATOMIC_EXPRESSION_FUNCTIONS = {
306
- **{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
+ },
307
350
  }
@@ -1,7 +1,7 @@
1
1
  import dataclasses
2
2
  from collections.abc import Sequence
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Callable
4
+ from typing import TYPE_CHECKING, Callable, Union, cast
5
5
 
6
6
  from typing_extensions import Self
7
7
 
@@ -15,11 +15,13 @@ 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
  )
21
22
  from classiq.interface.model.handle_binding import (
22
23
  HandleBinding,
24
+ HandlesList,
23
25
  NestedHandleBinding,
24
26
  SlicedHandleBinding,
25
27
  )
@@ -132,6 +134,7 @@ class _CapturedHandle(_Captured):
132
134
  name=self.mangled_name,
133
135
  quantum_type=self.quantum_type,
134
136
  direction=self.direction.dump(),
137
+ type_qualifier=TypeQualifier.Inferred, # TODO https://classiq.atlassian.net/browse/CLS-1830
135
138
  )
136
139
 
137
140
  def is_same_var(self, other: "_CapturedHandle") -> bool:
@@ -252,7 +255,7 @@ class CapturedVars:
252
255
 
253
256
  def _conjugate_direction(
254
257
  self,
255
- source_direction: PortDirection | bool,
258
+ source_direction: Union[PortDirection, bool],
256
259
  target_direction: PortDirection,
257
260
  var_name: str,
258
261
  ) -> PortDirection:
@@ -651,12 +654,9 @@ def validate_args_are_not_propagated(
651
654
  for handle in captured_vars
652
655
  if isinstance(handle, HandleBinding)
653
656
  }
654
- arg_handles = {
655
- demangle_handle(arg) for arg in args if isinstance(arg, HandleBinding)
656
- }
657
657
  violating_handles = [
658
658
  f"{str(arg_handle)!r}"
659
- for arg_handle in arg_handles
659
+ for arg_handle in _get_all_handles(args)
660
660
  if any(
661
661
  arg_handle.overlaps(captured_handle) for captured_handle in captured_handles
662
662
  )
@@ -667,6 +667,18 @@ def validate_args_are_not_propagated(
667
667
  )
668
668
 
669
669
 
670
+ def _get_all_handles(args: Sequence[ArgValue]) -> set[HandleBinding]:
671
+ arg_handles: set[HandleBinding] = set()
672
+ for arg in args:
673
+ if isinstance(arg, HandleBinding):
674
+ arg_handles.add(demangle_handle(arg))
675
+ elif isinstance(arg, HandlesList):
676
+ arg_handles |= set(
677
+ map(demangle_handle, cast(list[HandleBinding], arg.handles))
678
+ )
679
+ return arg_handles
680
+
681
+
670
682
  def validate_captured_directions(
671
683
  captured_vars: CapturedVars, report_outin: bool = True
672
684
  ) -> None:
@@ -17,6 +17,7 @@ from classiq.interface.generator.expressions.proxies.classical.classical_struct_
17
17
  from classiq.interface.generator.expressions.proxies.classical.utils import (
18
18
  get_proxy_type,
19
19
  )
20
+ from classiq.interface.helpers.pydantic_model_helpers import nameables_to_dict
20
21
  from classiq.interface.model.quantum_function_declaration import (
21
22
  NamedParamsQuantumFunctionDeclaration,
22
23
  PositionalArg,
@@ -43,6 +44,10 @@ class Closure:
43
44
  positional_arg_declarations: Sequence[PositionalArg] = tuple()
44
45
  captured_vars: CapturedVars = field(default_factory=CapturedVars)
45
46
 
47
+ @property
48
+ def parameters_dict(self) -> dict[str, PositionalArg]:
49
+ return nameables_to_dict(self.positional_arg_declarations)
50
+
46
51
 
47
52
  @dataclass(frozen=True)
48
53
  class GenerativeClosure(Closure):
@@ -27,7 +27,7 @@ from classiq.interface.model.quantum_function_declaration import (
27
27
 
28
28
  from classiq.model_expansions.closure import FunctionClosure
29
29
  from classiq.model_expansions.evaluators.type_type_match import check_signature_match
30
- from classiq.model_expansions.scope import Evaluated, QuantumSymbol
30
+ from classiq.model_expansions.scope import Evaluated, QuantumVariable
31
31
  from classiq.qmod.model_state_container import QMODULE
32
32
  from classiq.qmod.qmod_parameter import CInt, get_qmod_type
33
33
 
@@ -77,7 +77,7 @@ def check_arg_type_match(
77
77
 
78
78
 
79
79
  def _check_qvar_type_match(argument: Any, error_message: str) -> None:
80
- if not isinstance(argument, QuantumSymbol):
80
+ if not isinstance(argument, QuantumVariable):
81
81
  raise ClassiqExpansionError(error_message)
82
82
 
83
83
 
@@ -7,7 +7,7 @@ from classiq.interface.model.port_declaration import AnonPortDeclaration
7
7
  from classiq.interface.model.quantum_function_declaration import AnonPositionalArg
8
8
 
9
9
  from classiq.model_expansions.evaluators.quantum_type_utils import copy_type_information
10
- from classiq.model_expansions.scope import Evaluated, QuantumSymbol
10
+ from classiq.model_expansions.scope import Evaluated, QuantumVariable
11
11
 
12
12
 
13
13
  def add_information_from_output_arguments(
@@ -29,7 +29,7 @@ def add_information_from_output_arguments(
29
29
  if not isinstance(parameter, AnonPortDeclaration):
30
30
  continue
31
31
 
32
- argument_as_quantum_symbol = argument.as_type(QuantumSymbol)
32
+ argument_as_quantum_symbol = argument.as_type(QuantumVariable)
33
33
 
34
34
  if parameter.direction != PortDeclarationDirection.Output:
35
35
  continue
@@ -38,6 +38,6 @@ def add_information_from_output_arguments(
38
38
  copy_type_information(
39
39
  parameter.quantum_type,
40
40
  argument_as_quantum_symbol.quantum_type,
41
- str(argument_as_quantum_symbol.handle),
41
+ str(argument_as_quantum_symbol),
42
42
  )
43
43
  return args
@@ -5,6 +5,9 @@ from classiq.interface.generator.expressions.evaluated_expression import (
5
5
  )
6
6
  from classiq.interface.generator.expressions.expression import Expression
7
7
  from classiq.interface.generator.expressions.expression_types import ExpressionValue
8
+ from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
9
+ AnyClassicalValue,
10
+ )
8
11
  from classiq.interface.model.handle_binding import HandleBinding
9
12
 
10
13
  from classiq.model_expansions.expression_evaluator import evaluate
@@ -19,18 +22,15 @@ def evaluate_classical_expression(expr: Expression, scope: Scope) -> Evaluated:
19
22
  if isinstance(evaluated.value, get_args(ExpressionValue))
20
23
  } | {
21
24
  name: EvaluatedExpression(
22
- value=evaluated.value.quantum_type.get_proxy(HandleBinding(name=name))
25
+ value=(
26
+ evaluated.value.quantum_type.get_proxy(HandleBinding(name=name))
27
+ if evaluated.value.quantum_type.is_evaluated
28
+ else AnyClassicalValue(name)
29
+ )
23
30
  )
24
31
  for name, evaluated in all_symbols
25
32
  if isinstance(evaluated.value, QuantumSymbol)
26
- and evaluated.value.quantum_type.is_evaluated
27
- }
28
- uninitialized_locals = {
29
- name
30
- for name, evaluated in all_symbols
31
- if isinstance(evaluated.value, QuantumSymbol)
32
- and not evaluated.value.quantum_type.is_evaluated
33
33
  }
34
34
 
35
- ret = evaluate(expr, locals_dict, uninitialized_locals)
35
+ ret = evaluate(expr, locals_dict)
36
36
  return Evaluated(value=ret.value)