classiq 0.93.0__py3-none-any.whl → 0.94.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 (261) hide show
  1. classiq/__init__.py +6 -19
  2. classiq/_analyzer_extras/_ipywidgets_async_extension.py +7 -7
  3. classiq/_analyzer_extras/interactive_hardware.py +19 -12
  4. classiq/_internals/api_wrapper.py +38 -52
  5. classiq/_internals/async_utils.py +4 -7
  6. classiq/_internals/authentication/auth0.py +3 -3
  7. classiq/_internals/authentication/device.py +4 -4
  8. classiq/_internals/authentication/password_manager.py +13 -13
  9. classiq/_internals/authentication/token_manager.py +4 -5
  10. classiq/_internals/client.py +17 -44
  11. classiq/_internals/config.py +1 -2
  12. classiq/_internals/help.py +1 -2
  13. classiq/_internals/host_checker.py +3 -3
  14. classiq/_internals/jobs.py +14 -14
  15. classiq/_internals/type_validation.py +3 -3
  16. classiq/analyzer/analyzer.py +18 -18
  17. classiq/analyzer/rb.py +17 -8
  18. classiq/applications/chemistry/__init__.py +0 -30
  19. classiq/applications/chemistry/op_utils.py +4 -4
  20. classiq/applications/chemistry/problems.py +3 -3
  21. classiq/applications/chemistry/ucc.py +1 -2
  22. classiq/applications/chemistry/z2_symmetries.py +4 -4
  23. classiq/applications/combinatorial_helpers/allowed_constraints.py +1 -3
  24. classiq/applications/combinatorial_helpers/arithmetic/arithmetic_expression.py +2 -1
  25. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +2 -2
  26. classiq/applications/combinatorial_helpers/encoding_mapping.py +2 -3
  27. classiq/applications/combinatorial_helpers/encoding_utils.py +2 -2
  28. classiq/applications/combinatorial_helpers/optimization_model.py +3 -4
  29. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_sparsing.py +2 -2
  30. classiq/applications/combinatorial_helpers/pyomo_utils.py +8 -8
  31. classiq/applications/combinatorial_helpers/sympy_utils.py +1 -3
  32. classiq/applications/combinatorial_helpers/transformations/encoding.py +3 -3
  33. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +1 -2
  34. classiq/applications/combinatorial_optimization/combinatorial_optimization_config.py +2 -3
  35. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +4 -6
  36. classiq/applications/combinatorial_optimization/combinatorial_problem.py +15 -10
  37. classiq/applications/hamiltonian/pauli_decomposition.py +6 -4
  38. classiq/applications/iqae/iqae.py +8 -8
  39. classiq/applications/qnn/datasets/dataset_base_classes.py +6 -6
  40. classiq/applications/qnn/datasets/dataset_parity.py +6 -6
  41. classiq/applications/qnn/qlayer.py +8 -7
  42. classiq/applications/qnn/torch_utils.py +3 -4
  43. classiq/applications/qnn/types.py +2 -1
  44. classiq/applications/qsp/qsp.py +5 -4
  45. classiq/applications/qsvm/qsvm_data_generation.py +1 -2
  46. classiq/evaluators/classical_expression.py +0 -4
  47. classiq/evaluators/parameter_types.py +7 -8
  48. classiq/evaluators/qmod_annotated_expression.py +24 -26
  49. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +14 -14
  50. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +2 -1
  51. classiq/evaluators/qmod_expression_visitors/sympy_wrappers.py +8 -8
  52. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +4 -4
  53. classiq/evaluators/qmod_node_evaluators/list_evaluation.py +2 -2
  54. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +3 -3
  55. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +9 -9
  56. classiq/evaluators/qmod_node_evaluators/utils.py +6 -6
  57. classiq/evaluators/qmod_type_inference/classical_type_inference.py +9 -10
  58. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +5 -5
  59. classiq/execution/execution_session.py +18 -19
  60. classiq/execution/jobs.py +26 -26
  61. classiq/execution/qnn.py +1 -2
  62. classiq/execution/user_budgets.py +52 -7
  63. classiq/executor.py +1 -3
  64. classiq/interface/_version.py +1 -1
  65. classiq/interface/analyzer/analysis_params.py +4 -4
  66. classiq/interface/analyzer/cytoscape_graph.py +3 -3
  67. classiq/interface/analyzer/result.py +4 -4
  68. classiq/interface/applications/qsvm.py +5 -8
  69. classiq/interface/ast_node.py +3 -3
  70. classiq/interface/backend/backend_preferences.py +16 -16
  71. classiq/interface/backend/ionq/ionq_quantum_program.py +5 -5
  72. classiq/interface/chemistry/ansatz_library.py +3 -5
  73. classiq/interface/chemistry/operator.py +3 -3
  74. classiq/interface/combinatorial_optimization/examples/knapsack.py +2 -4
  75. classiq/interface/combinatorial_optimization/examples/tsp_digraph.py +1 -2
  76. classiq/interface/compression_utils.py +2 -3
  77. classiq/interface/debug_info/debug_info.py +7 -7
  78. classiq/interface/exceptions.py +2 -3
  79. classiq/interface/execution/iqcc.py +1 -3
  80. classiq/interface/execution/primitives.py +6 -6
  81. classiq/interface/executor/estimate_cost.py +1 -1
  82. classiq/interface/executor/execution_preferences.py +3 -5
  83. classiq/interface/executor/execution_request.py +10 -10
  84. classiq/interface/executor/execution_result.py +1 -2
  85. classiq/interface/executor/quantum_code.py +8 -8
  86. classiq/interface/executor/result.py +28 -18
  87. classiq/interface/executor/user_budget.py +2 -3
  88. classiq/interface/executor/vqe_result.py +5 -6
  89. classiq/interface/generator/ansatz_library.py +6 -8
  90. classiq/interface/generator/application_apis/__init__.py +0 -2
  91. classiq/interface/generator/arith/arithmetic.py +2 -2
  92. classiq/interface/generator/arith/arithmetic_arg_type_validator.py +2 -3
  93. classiq/interface/generator/arith/arithmetic_expression_abc.py +4 -5
  94. classiq/interface/generator/arith/arithmetic_expression_parser.py +11 -4
  95. classiq/interface/generator/arith/arithmetic_expression_validator.py +12 -15
  96. classiq/interface/generator/arith/arithmetic_operations.py +4 -6
  97. classiq/interface/generator/arith/arithmetic_param_getters.py +70 -107
  98. classiq/interface/generator/arith/arithmetic_result_builder.py +4 -4
  99. classiq/interface/generator/arith/ast_node_rewrite.py +8 -4
  100. classiq/interface/generator/arith/binary_ops.py +7 -36
  101. classiq/interface/generator/arith/logical_ops.py +2 -3
  102. classiq/interface/generator/arith/number_utils.py +2 -2
  103. classiq/interface/generator/arith/register_user_input.py +2 -2
  104. classiq/interface/generator/arith/unary_ops.py +2 -2
  105. classiq/interface/generator/circuit_code/circuit_code.py +8 -10
  106. classiq/interface/generator/circuit_code/types_and_constants.py +1 -1
  107. classiq/interface/generator/complex_type.py +2 -2
  108. classiq/interface/generator/copy.py +1 -3
  109. classiq/interface/generator/expressions/atomic_expression_functions.py +0 -5
  110. classiq/interface/generator/expressions/evaluated_expression.py +2 -3
  111. classiq/interface/generator/expressions/expression.py +2 -2
  112. classiq/interface/generator/expressions/proxies/classical/classical_array_proxy.py +4 -7
  113. classiq/interface/generator/function_param_list.py +0 -20
  114. classiq/interface/generator/function_params.py +5 -6
  115. classiq/interface/generator/functions/classical_function_declaration.py +2 -2
  116. classiq/interface/generator/functions/classical_type.py +3 -3
  117. classiq/interface/generator/functions/type_modifier.py +0 -14
  118. classiq/interface/generator/functions/type_name.py +2 -2
  119. classiq/interface/generator/generated_circuit_data.py +12 -13
  120. classiq/interface/generator/hamiltonian_evolution/exponentiation.py +2 -4
  121. classiq/interface/generator/hardware/hardware_data.py +8 -8
  122. classiq/interface/generator/hardware_efficient_ansatz.py +8 -8
  123. classiq/interface/generator/mcu.py +3 -3
  124. classiq/interface/generator/mcx.py +3 -3
  125. classiq/interface/generator/model/constraints.py +34 -5
  126. classiq/interface/generator/model/preferences/preferences.py +15 -21
  127. classiq/interface/generator/model/quantum_register.py +7 -10
  128. classiq/interface/generator/noise_properties.py +3 -7
  129. classiq/interface/generator/parameters.py +1 -1
  130. classiq/interface/generator/partitioned_register.py +1 -2
  131. classiq/interface/generator/preferences/qasm_to_qmod_params.py +11 -0
  132. classiq/interface/generator/qsvm.py +2 -2
  133. classiq/interface/generator/quantum_function_call.py +8 -11
  134. classiq/interface/generator/quantum_program.py +12 -15
  135. classiq/interface/generator/range_types.py +3 -3
  136. classiq/interface/generator/slice_parsing_utils.py +4 -5
  137. classiq/interface/generator/standard_gates/standard_gates.py +2 -4
  138. classiq/interface/generator/state_preparation/state_preparation.py +6 -8
  139. classiq/interface/generator/synthesis_execution_parameter.py +1 -3
  140. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +2 -3
  141. classiq/interface/generator/transpiler_basis_gates.py +2 -4
  142. classiq/interface/generator/types/builtin_enum_declarations.py +0 -136
  143. classiq/interface/generator/types/compilation_metadata.py +12 -1
  144. classiq/interface/generator/types/enum_declaration.py +2 -1
  145. classiq/interface/generator/validations/flow_graph.py +3 -3
  146. classiq/interface/generator/visitor.py +10 -12
  147. classiq/interface/hardware.py +2 -2
  148. classiq/interface/helpers/classproperty.py +2 -2
  149. classiq/interface/helpers/custom_encoders.py +2 -1
  150. classiq/interface/helpers/custom_pydantic_types.py +1 -1
  151. classiq/interface/helpers/text_utils.py +1 -4
  152. classiq/interface/ide/visual_model.py +5 -5
  153. classiq/interface/jobs.py +3 -3
  154. classiq/interface/model/allocate.py +4 -4
  155. classiq/interface/model/block.py +2 -2
  156. classiq/interface/model/bounds.py +3 -3
  157. classiq/interface/model/control.py +1 -1
  158. classiq/interface/model/inplace_binary_operation.py +2 -2
  159. classiq/interface/model/model.py +4 -4
  160. classiq/interface/model/parameter.py +1 -3
  161. classiq/interface/model/port_declaration.py +1 -1
  162. classiq/interface/model/quantum_expressions/quantum_expression.py +1 -2
  163. classiq/interface/model/quantum_function_call.py +3 -6
  164. classiq/interface/model/quantum_function_declaration.py +1 -0
  165. classiq/interface/model/quantum_lambda_function.py +4 -4
  166. classiq/interface/model/quantum_statement.py +4 -4
  167. classiq/interface/model/quantum_type.py +14 -14
  168. classiq/interface/model/validation_handle.py +2 -3
  169. classiq/interface/model/variable_declaration_statement.py +2 -2
  170. classiq/interface/pretty_print/expression_to_qmod.py +3 -4
  171. classiq/interface/server/routes.py +0 -4
  172. classiq/interface/source_reference.py +3 -4
  173. classiq/model_expansions/arithmetic.py +6 -7
  174. classiq/model_expansions/arithmetic_compute_result_attrs.py +4 -5
  175. classiq/model_expansions/capturing/captured_vars.py +3 -3
  176. classiq/model_expansions/capturing/mangling_utils.py +1 -2
  177. classiq/model_expansions/closure.py +12 -11
  178. classiq/model_expansions/function_builder.py +14 -6
  179. classiq/model_expansions/generative_functions.py +1 -4
  180. classiq/model_expansions/interpreters/base_interpreter.py +2 -6
  181. classiq/model_expansions/interpreters/generative_interpreter.py +5 -3
  182. classiq/model_expansions/quantum_operations/allocate.py +4 -4
  183. classiq/model_expansions/quantum_operations/assignment_result_processor.py +2 -4
  184. classiq/model_expansions/quantum_operations/call_emitter.py +31 -37
  185. classiq/model_expansions/quantum_operations/declarative_call_emitter.py +2 -2
  186. classiq/model_expansions/quantum_operations/emitter.py +3 -5
  187. classiq/model_expansions/quantum_operations/expression_evaluator.py +3 -3
  188. classiq/model_expansions/quantum_operations/skip_control_verifier.py +1 -2
  189. classiq/model_expansions/quantum_operations/variable_decleration.py +2 -2
  190. classiq/model_expansions/scope.py +7 -7
  191. classiq/model_expansions/scope_initialization.py +4 -0
  192. classiq/model_expansions/visitors/symbolic_param_inference.py +3 -3
  193. classiq/model_expansions/visitors/uncomputation_signature_inference.py +317 -0
  194. classiq/model_expansions/visitors/variable_references.py +15 -14
  195. classiq/open_library/functions/__init__.py +6 -0
  196. classiq/open_library/functions/discrete_sine_cosine_transform.py +5 -5
  197. classiq/open_library/functions/grover.py +8 -10
  198. classiq/open_library/functions/modular_exponentiation.py +96 -8
  199. classiq/qmod/__init__.py +5 -2
  200. classiq/qmod/builtins/classical_execution_primitives.py +4 -11
  201. classiq/qmod/builtins/classical_functions.py +1 -42
  202. classiq/qmod/builtins/enums.py +0 -136
  203. classiq/qmod/builtins/functions/__init__.py +0 -13
  204. classiq/qmod/builtins/functions/allocation.py +4 -4
  205. classiq/qmod/builtins/functions/arithmetic.py +22 -27
  206. classiq/qmod/builtins/functions/standard_gates.py +27 -27
  207. classiq/qmod/builtins/operations.py +35 -58
  208. classiq/qmod/builtins/structs.py +2 -58
  209. classiq/qmod/cfunc.py +3 -2
  210. classiq/qmod/classical_function.py +2 -1
  211. classiq/qmod/cparam.py +2 -8
  212. classiq/qmod/create_model_function.py +7 -7
  213. classiq/qmod/declaration_inferrer.py +33 -30
  214. classiq/qmod/model_state_container.py +2 -2
  215. classiq/qmod/native/pretty_printer.py +25 -14
  216. classiq/qmod/pretty_print/expression_to_python.py +5 -3
  217. classiq/qmod/pretty_print/pretty_printer.py +39 -17
  218. classiq/qmod/python_classical_type.py +40 -13
  219. classiq/qmod/qfunc.py +139 -16
  220. classiq/qmod/qmod_constant.py +2 -2
  221. classiq/qmod/qmod_parameter.py +5 -2
  222. classiq/qmod/qmod_variable.py +47 -43
  223. classiq/qmod/quantum_callable.py +18 -13
  224. classiq/qmod/quantum_expandable.py +31 -26
  225. classiq/qmod/quantum_function.py +51 -32
  226. classiq/qmod/semantics/annotation/call_annotation.py +2 -2
  227. classiq/qmod/semantics/error_manager.py +5 -6
  228. classiq/qmod/semantics/lambdas.py +1 -2
  229. classiq/qmod/semantics/validation/types_validation.py +1 -2
  230. classiq/qmod/symbolic.py +2 -4
  231. classiq/qmod/utilities.py +13 -10
  232. classiq/qmod/write_qmod.py +3 -4
  233. classiq/quantum_program.py +1 -3
  234. classiq/synthesis.py +11 -7
  235. {classiq-0.93.0.dist-info → classiq-0.94.0.dist-info}/METADATA +2 -3
  236. {classiq-0.93.0.dist-info → classiq-0.94.0.dist-info}/RECORD +238 -260
  237. classiq/applications/chemistry/ansatz_parameters.py +0 -29
  238. classiq/applications/chemistry/chemistry_execution_parameters.py +0 -16
  239. classiq/applications/chemistry/chemistry_model_constructor.py +0 -532
  240. classiq/applications/chemistry/ground_state_problem.py +0 -42
  241. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +0 -129
  242. classiq/interface/chemistry/elements.py +0 -120
  243. classiq/interface/chemistry/fermionic_operator.py +0 -208
  244. classiq/interface/chemistry/ground_state_problem.py +0 -132
  245. classiq/interface/chemistry/ground_state_result.py +0 -8
  246. classiq/interface/chemistry/molecule.py +0 -71
  247. classiq/interface/generator/application_apis/chemistry_declarations.py +0 -69
  248. classiq/interface/generator/application_apis/entangler_declarations.py +0 -29
  249. classiq/interface/generator/chemistry_function_params.py +0 -50
  250. classiq/interface/generator/entangler_params.py +0 -72
  251. classiq/interface/generator/entanglers.py +0 -14
  252. classiq/interface/generator/hartree_fock.py +0 -26
  253. classiq/interface/generator/hva.py +0 -22
  254. classiq/interface/generator/linear_pauli_rotations.py +0 -92
  255. classiq/interface/generator/qft.py +0 -37
  256. classiq/interface/generator/ucc.py +0 -74
  257. classiq/interface/helpers/backward_compatibility.py +0 -9
  258. classiq/model_expansions/transformers/type_modifier_inference.py +0 -392
  259. classiq/qmod/builtins/functions/chemistry.py +0 -123
  260. {classiq-0.93.0.dist-info → classiq-0.94.0.dist-info}/WHEEL +0 -0
  261. {classiq-0.93.0.dist-info → classiq-0.94.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING, Union, cast
1
+ from typing import TYPE_CHECKING, cast
2
2
 
3
3
  from classiq.interface.exceptions import ClassiqExpansionError
4
4
  from classiq.interface.generator.functions.classical_type import ClassicalType
@@ -38,7 +38,7 @@ class VariableDeclarationStatementEmitter(Emitter[VariableDeclarationStatement])
38
38
  raise ClassiqExpansionError(
39
39
  f"Variable {variable_declaration.name!r} is already defined"
40
40
  )
41
- var_value: Union[QuantumSymbol, ClassicalSymbol]
41
+ var_value: QuantumSymbol | ClassicalSymbol
42
42
  if variable_declaration.is_quantum:
43
43
  var_value = self._get_quantum_var(var_decl)
44
44
  else:
@@ -68,7 +68,7 @@ class QuantumSymbol(QuantumVariable):
68
68
  return self.handle
69
69
 
70
70
  def __getitem__(
71
- self, item: Union[slice, int, QmodAnnotatedExpression]
71
+ self, item: slice | int | QmodAnnotatedExpression
72
72
  ) -> "QuantumSymbol":
73
73
  if isinstance(item, slice):
74
74
  return self._slice(item.start, item.stop)
@@ -76,8 +76,8 @@ class QuantumSymbol(QuantumVariable):
76
76
 
77
77
  def _slice(
78
78
  self,
79
- start: Union[int, QmodAnnotatedExpression],
80
- end: Union[int, QmodAnnotatedExpression],
79
+ start: int | QmodAnnotatedExpression,
80
+ end: int | QmodAnnotatedExpression,
81
81
  ) -> "QuantumSymbol":
82
82
  if not isinstance(self.quantum_type, QuantumBitvector):
83
83
  raise ClassiqExpansionError(
@@ -119,7 +119,7 @@ class QuantumSymbol(QuantumVariable):
119
119
  ),
120
120
  )
121
121
 
122
- def _subscript(self, index: Union[int, QmodAnnotatedExpression]) -> "QuantumSymbol":
122
+ def _subscript(self, index: int | QmodAnnotatedExpression) -> "QuantumSymbol":
123
123
  if not isinstance(self.quantum_type, QuantumBitvector):
124
124
  raise ClassiqExpansionError(
125
125
  f"{self.quantum_type.type_name} is not subscriptable"
@@ -188,7 +188,7 @@ class QuantumSymbolList(QuantumVariable):
188
188
  )
189
189
  if len(handles) == 0:
190
190
  raise ClassiqExpansionError("Empty concatenation expression")
191
- length: Optional[Expression]
191
+ length: Expression | None
192
192
  if any(not symbol.quantum_type.has_size_in_bits for symbol in symbols):
193
193
  length = None
194
194
  else:
@@ -252,7 +252,7 @@ class Evaluated: # FIXME: Merge with EvaluatedExpression if possible
252
252
 
253
253
  return value
254
254
 
255
- def emit(self, param: Optional[AnonPositionalArg] = None) -> ArgValue:
255
+ def emit(self, param: AnonPositionalArg | None = None) -> ArgValue:
256
256
  from classiq.model_expansions.closure import FunctionClosure
257
257
 
258
258
  if isinstance(self.value, (QuantumVariable, FunctionClosure)):
@@ -278,7 +278,7 @@ else:
278
278
  class Scope(EvaluatedUserDict):
279
279
  def __init__(
280
280
  self,
281
- data: Optional[dict[str, Evaluated]] = None,
281
+ data: dict[str, Evaluated] | None = None,
282
282
  /,
283
283
  *,
284
284
  parent: Optional["Scope"] = None,
@@ -44,6 +44,7 @@ def add_functions_to_scope(
44
44
  value=FunctionClosure.create(
45
45
  name=function.name,
46
46
  positional_arg_declarations=function.positional_arg_declarations,
47
+ permutation=function.permutation,
47
48
  body=function.body,
48
49
  scope=Scope(parent=scope),
49
50
  )
@@ -64,6 +65,7 @@ def add_generative_functions_to_scope(
64
65
  value=GenerativeFunctionClosure.create(
65
66
  name=name,
66
67
  positional_arg_declarations=function.func_decl.positional_arg_declarations,
68
+ permutation=function.permutation,
67
69
  scope=Scope(parent=scope),
68
70
  generative_blocks={"body": function},
69
71
  )
@@ -76,6 +78,7 @@ def _init_builtins_scope(scope: Scope) -> None:
76
78
  value=FunctionClosure.create(
77
79
  name=builtin_function.name,
78
80
  positional_arg_declarations=builtin_function.positional_arg_declarations,
81
+ permutation=builtin_function.permutation,
79
82
  scope=Scope(parent=scope),
80
83
  is_atomic=True,
81
84
  )
@@ -85,6 +88,7 @@ def _init_builtins_scope(scope: Scope) -> None:
85
88
  value=FunctionClosure.create(
86
89
  name=builtin_function.name,
87
90
  positional_arg_declarations=builtin_function.positional_arg_declarations,
91
+ permutation=builtin_function.permutation,
88
92
  scope=Scope(parent=scope),
89
93
  )
90
94
  )
@@ -2,7 +2,7 @@ import ast
2
2
  from collections.abc import Iterator, Mapping, Sequence
3
3
  from contextlib import contextmanager
4
4
  from itertools import chain, zip_longest
5
- from typing import Optional, cast
5
+ from typing import cast
6
6
 
7
7
  from classiq.interface.generator.expressions.atomic_expression_functions import (
8
8
  CLASSICAL_ATTRIBUTES,
@@ -88,9 +88,9 @@ class SymbolicParamInference(ModelVisitor):
88
88
  def __init__(
89
89
  self,
90
90
  functions: list[NativeFunctionDefinition],
91
- additional_signatures: Optional[
91
+ additional_signatures: None | (
92
92
  list[NamedParamsQuantumFunctionDeclaration]
93
- ] = None,
93
+ ) = None,
94
94
  ) -> None:
95
95
  self._functions = nameables_to_dict(functions)
96
96
  self._additional_signatures = (
@@ -0,0 +1,317 @@
1
+ import warnings
2
+ from collections import defaultdict
3
+ from collections.abc import Iterator
4
+ from contextlib import contextmanager
5
+ from typing import NamedTuple
6
+
7
+ from classiq.interface.ast_node import ASTNode
8
+ from classiq.interface.exceptions import (
9
+ ClassiqDeprecationWarning,
10
+ ClassiqInternalExpansionError,
11
+ )
12
+ from classiq.interface.generator.compiler_keywords import EXPANDED_KEYWORD
13
+ from classiq.interface.generator.functions.port_declaration import (
14
+ PortDeclarationDirection,
15
+ )
16
+ from classiq.interface.generator.functions.type_modifier import TypeModifier
17
+ from classiq.interface.generator.visitor import NodeType
18
+ from classiq.interface.model.allocate import Allocate
19
+ from classiq.interface.model.bind_operation import BindOperation
20
+ from classiq.interface.model.block import Block
21
+ from classiq.interface.model.control import Control
22
+ from classiq.interface.model.invert import Invert
23
+ from classiq.interface.model.model_visitor import ModelVisitor
24
+ from classiq.interface.model.native_function_definition import NativeFunctionDefinition
25
+ from classiq.interface.model.power import Power
26
+ from classiq.interface.model.quantum_expressions.amplitude_loading_operation import (
27
+ AmplitudeLoadingOperation,
28
+ )
29
+ from classiq.interface.model.quantum_expressions.arithmetic_operation import (
30
+ ArithmeticOperation,
31
+ )
32
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
33
+ from classiq.interface.model.skip_control import SkipControl
34
+ from classiq.interface.model.within_apply_operation import WithinApply
35
+ from classiq.interface.source_reference import SourceReference
36
+
37
+
38
+ class _BoundVars(NamedTuple):
39
+ in_identifiers: list[str]
40
+ out_identifiers: list[str]
41
+ source_ref: SourceReference | None
42
+
43
+ def reverse(self) -> "_BoundVars":
44
+ return _BoundVars(
45
+ in_identifiers=self.out_identifiers,
46
+ out_identifiers=self.in_identifiers,
47
+ source_ref=self.source_ref,
48
+ )
49
+
50
+
51
+ class UncomputationSignatureInference(ModelVisitor):
52
+ """
53
+ Infers the uncomputation signature of a function (permutation/non-permutation for
54
+ the function, and const/non-const for each parameter).
55
+
56
+ A function is a permutation if and only if all its body operations are permutations
57
+ (note that amplitude loading operation is not a permutation).
58
+
59
+ A parameter is const if and only if it is used as a const argument in all the
60
+ body operations (including when binding it to a different variable). An exception
61
+ for this rule is that a const parameter can be used as a non-const argument to a
62
+ permutation function inside a `within` block.
63
+
64
+ This class assumes that dependent functions are already inferred, so it doesn't
65
+ recursively inferring function calls.
66
+ """
67
+
68
+ def __init__(self) -> None:
69
+ self._is_permutation: bool = True
70
+ self._non_permutation_reasons: list[SourceReference | None] = []
71
+ self._non_const_with_reasons: dict[str, list[SourceReference | None]] = (
72
+ defaultdict(list)
73
+ )
74
+
75
+ self._in_conjugation: bool = False
76
+ self._source_ref: SourceReference | None = None
77
+
78
+ # remember bound vars inside `within` to invert their effect after the `apply`
79
+ self._bound_vars_list: list[_BoundVars] = []
80
+
81
+ def run(self, func_def: NativeFunctionDefinition) -> None:
82
+ self._is_permutation = True
83
+ self._non_permutation_reasons.clear()
84
+ self._non_const_with_reasons.clear()
85
+ self.visit(func_def.body)
86
+
87
+ def is_permutation(self) -> bool:
88
+ return self._is_permutation
89
+
90
+ def non_permutation_reasons(self) -> list[SourceReference | None]:
91
+ if self._is_permutation:
92
+ raise ClassiqInternalExpansionError("Function is a permutation")
93
+ return self._non_permutation_reasons
94
+
95
+ def is_const(self, port: str) -> bool:
96
+ return port not in self._non_const_with_reasons
97
+
98
+ def non_const_reasons(self, port: str) -> list[SourceReference | None]:
99
+ if port not in self._non_const_with_reasons:
100
+ raise ClassiqInternalExpansionError("Parameter is constant")
101
+ return self._non_const_with_reasons[port]
102
+
103
+ def visit(self, node: NodeType) -> None:
104
+ if isinstance(node, ASTNode):
105
+ with self._source_reference_context(node.source_ref):
106
+ super().visit(node)
107
+ else:
108
+ super().visit(node)
109
+
110
+ def visit_QuantumFunctionCall(self, call: QuantumFunctionCall) -> None:
111
+ if not call.func_decl.permutation:
112
+ self._mark_as_non_permutation()
113
+
114
+ in_identifiers: list[str] = []
115
+ out_identifiers: list[str] = []
116
+
117
+ for handle, port in call.handles_with_params:
118
+ if port.type_modifier is not TypeModifier.Const:
119
+ self._mark_as_non_const(handle.name, call.func_decl.permutation)
120
+
121
+ if port.direction is PortDeclarationDirection.Input:
122
+ in_identifiers.append(handle.name)
123
+ elif port.direction is PortDeclarationDirection.Output:
124
+ out_identifiers.append(handle.name)
125
+
126
+ if in_identifiers or out_identifiers:
127
+ bound_vars = _BoundVars(in_identifiers, out_identifiers, call.source_ref)
128
+ self._mark_bind_outputs(bound_vars)
129
+ self._bound_vars_list.append(bound_vars)
130
+
131
+ def visit_Allocate(self, alloc: Allocate) -> None:
132
+ self._mark_as_non_const(alloc.target.name, True)
133
+
134
+ def visit_BindOperation(self, bind_op: BindOperation) -> None:
135
+ in_identifiers = [handle.name for handle in bind_op.in_handles]
136
+ out_identifiers = [handle.name for handle in bind_op.out_handles]
137
+ bound_vars = _BoundVars(in_identifiers, out_identifiers, bind_op.source_ref)
138
+ self._mark_bind_outputs(bound_vars)
139
+ self._bound_vars_list.append(bound_vars)
140
+
141
+ def visit_ArithmeticOperation(self, arith: ArithmeticOperation) -> None:
142
+ self._mark_as_non_const(arith.result_var.name, True)
143
+
144
+ def visit_AmplitudeLoadingOperation(
145
+ self, amp_load: AmplitudeLoadingOperation
146
+ ) -> None:
147
+ self._mark_as_non_permutation()
148
+ self._mark_as_non_const(amp_load.result_var.name, False)
149
+
150
+ def visit_Control(self, control: Control) -> None:
151
+ self.visit(control.body)
152
+ if control.else_block is not None:
153
+ self.visit(control.else_block)
154
+
155
+ def visit_Invert(self, invert: Invert) -> None:
156
+ self.visit(invert.body)
157
+
158
+ def visit_Power(self, power: Power) -> None:
159
+ self.visit(power.body)
160
+
161
+ def visit_WithinApply(self, within_apply: WithinApply) -> None:
162
+ with self._conjugation_context() as bound_vars_list:
163
+ self.visit(within_apply.compute)
164
+ self.visit(within_apply.action)
165
+
166
+ for bound_vars in reversed(bound_vars_list):
167
+ self._mark_bind_outputs(bound_vars.reverse())
168
+
169
+ def visit_Block(self, block: Block) -> None:
170
+ self.visit(block.statements)
171
+
172
+ def visit_SkipControl(self, block: SkipControl) -> None:
173
+ self.visit(block.body)
174
+
175
+ def _mark_as_non_permutation(self) -> None:
176
+ self._is_permutation = False
177
+ self._non_permutation_reasons.append(self._source_ref)
178
+
179
+ def _mark_as_non_const(
180
+ self,
181
+ identifier: str,
182
+ permutation_op: bool,
183
+ source_ref: SourceReference | None = None,
184
+ ) -> None:
185
+ if self._in_conjugation and permutation_op:
186
+ return
187
+ self._non_const_with_reasons[identifier].append(source_ref or self._source_ref)
188
+
189
+ def _mark_bind_outputs(self, bound_vars: _BoundVars) -> None:
190
+ if all(self.is_const(identifier) for identifier in bound_vars.in_identifiers):
191
+ return
192
+ for identifier in bound_vars.out_identifiers:
193
+ self._mark_as_non_const(identifier, False, bound_vars.source_ref)
194
+
195
+ @contextmanager
196
+ def _conjugation_context(self) -> Iterator[list[_BoundVars]]:
197
+ previous_bound_vars = self._bound_vars_list
198
+ previous_context = self._in_conjugation
199
+ self._bound_vars_list = []
200
+ self._in_conjugation = True
201
+ try:
202
+ yield self._bound_vars_list
203
+ finally:
204
+ self._in_conjugation = previous_context
205
+ self._bound_vars_list = previous_bound_vars
206
+
207
+ @contextmanager
208
+ def _source_reference_context(
209
+ self, source_ref: SourceReference | None
210
+ ) -> Iterator[None]:
211
+ previous_source_ref = self._source_ref
212
+ self._source_ref = source_ref
213
+ try:
214
+ yield
215
+ finally:
216
+ self._source_ref = previous_source_ref
217
+
218
+
219
+ def infer_and_validate_uncomputation_signature(
220
+ func_def: NativeFunctionDefinition,
221
+ disable_perm_check: bool = False,
222
+ disable_const_checks: list[str] | bool = False,
223
+ tighten_signature: bool = False,
224
+ ) -> None:
225
+ """
226
+ Runs the uncomputation signature inference in order to validate the function signature
227
+ and tighten it when requested (changing non-permutation to permutation and non-const
228
+ to const).
229
+ """
230
+ for port in func_def.port_declarations:
231
+ if port.type_modifier is TypeModifier.Const and port.direction in (
232
+ PortDeclarationDirection.Input,
233
+ PortDeclarationDirection.Output,
234
+ ):
235
+ warnings.warn(
236
+ _input_output_const(port.name, port.direction, func_def.name),
237
+ ClassiqDeprecationWarning,
238
+ stacklevel=1,
239
+ )
240
+
241
+ if disable_perm_check and (disable_const_checks is True) and not tighten_signature:
242
+ return
243
+
244
+ visitor = UncomputationSignatureInference()
245
+ visitor.run(func_def)
246
+
247
+ if not disable_perm_check and func_def.permutation and not visitor.is_permutation():
248
+ for source_ref in visitor.non_permutation_reasons():
249
+ warnings.warn(
250
+ _non_permutation_usage(func_def.name, source_ref),
251
+ ClassiqDeprecationWarning,
252
+ stacklevel=1,
253
+ )
254
+
255
+ if tighten_signature and not func_def.permutation and visitor.is_permutation():
256
+ func_def.permutation = True
257
+
258
+ unchecked = (
259
+ set(disable_const_checks) if isinstance(disable_const_checks, list) else set()
260
+ )
261
+ for port in func_def.port_declarations:
262
+ if (
263
+ not ((disable_const_checks is True) or port.name in unchecked)
264
+ and port.type_modifier is TypeModifier.Const
265
+ and not visitor.is_const(port.name)
266
+ ):
267
+ for source_ref in visitor.non_const_reasons(port.name):
268
+ warnings.warn(
269
+ _non_const_usage(port.name, source_ref),
270
+ ClassiqDeprecationWarning,
271
+ stacklevel=1,
272
+ )
273
+
274
+ if (
275
+ tighten_signature
276
+ and port.type_modifier is not TypeModifier.Const
277
+ and visitor.is_const(port.name)
278
+ ):
279
+ port.type_modifier = TypeModifier.Const
280
+
281
+
282
+ def _input_output_const(
283
+ port_name: str,
284
+ direction: PortDeclarationDirection,
285
+ function_name: str,
286
+ ) -> str:
287
+ return (
288
+ f"{direction.capitalize()} parameters cannot be defined as constants"
289
+ f" (parameter {port_name!r} in function {function_name.split('_' + EXPANDED_KEYWORD)[0]!r}).\n"
290
+ "The deprecation warning will be elevated to an error starting 2025-10-30, at the earliest."
291
+ )
292
+
293
+
294
+ def _non_const_usage(
295
+ port_name: str,
296
+ source_ref: SourceReference | None = None,
297
+ ) -> str:
298
+ source_ref_str = f"\n\tat {source_ref}" if source_ref else ""
299
+ return (
300
+ f"Non-constant usage of a constant parameter {port_name!r}.{source_ref_str}\n"
301
+ "Tip: if the commulative use of the parameter in the function is constant, "
302
+ "use the `disable_const_checks` flag to instruct the compiler to disregard individual operations.\n"
303
+ "The deprecation warning will be elevated to an error starting 2025-10-30, at the earliest."
304
+ )
305
+
306
+
307
+ def _non_permutation_usage(
308
+ function_name: str,
309
+ source_ref: SourceReference | None = None,
310
+ ) -> str:
311
+ source_ref_str = f"\n\tat {source_ref}" if source_ref else ""
312
+ return (
313
+ f"Non-permutation operation used in a permutation function {function_name.split('_' + EXPANDED_KEYWORD)[0]!r}.{source_ref_str}\n"
314
+ "Tip: if the commulative effect of the function is a permutation, "
315
+ "use the `disable_perm_check` flag to instruct the compiler to disregard individual operations.\n"
316
+ "The deprecation warning will be elevated to an error starting 2025-10-30, at the earliest."
317
+ )
@@ -1,7 +1,6 @@
1
1
  import ast
2
2
  from collections.abc import Iterator
3
3
  from contextlib import contextmanager
4
- from typing import Optional, Union
5
4
 
6
5
  from classiq.interface.exceptions import (
7
6
  ClassiqExpansionError,
@@ -46,13 +45,15 @@ class VarRefCollector(ast.NodeVisitor):
46
45
  handle for handle, in_subscript in self._var_handles.items() if in_subscript
47
46
  ]
48
47
 
49
- def visit(self, node: ast.AST) -> Union[
50
- SubscriptHandleBinding,
51
- SlicedHandleBinding,
52
- FieldHandleBinding,
53
- HandleBinding,
54
- None,
55
- ]:
48
+ def visit(
49
+ self, node: ast.AST
50
+ ) -> (
51
+ SubscriptHandleBinding
52
+ | SlicedHandleBinding
53
+ | FieldHandleBinding
54
+ | HandleBinding
55
+ | None
56
+ ):
56
57
  res = super().visit(node)
57
58
  if not self._ignore_duplicated_handles and len(self._var_handles) != len(
58
59
  {handle.name for handle in self._var_handles}
@@ -62,13 +63,13 @@ class VarRefCollector(ast.NodeVisitor):
62
63
  )
63
64
  return res
64
65
 
65
- def visit_Subscript(self, node: ast.Subscript) -> Union[HandleBinding, None]:
66
+ def visit_Subscript(self, node: ast.Subscript) -> HandleBinding | None:
66
67
  return self._get_subscript_handle(node.value, node.slice)
67
68
 
68
- def visit_Attribute(self, node: ast.Attribute) -> Optional[FieldHandleBinding]:
69
+ def visit_Attribute(self, node: ast.Attribute) -> FieldHandleBinding | None:
69
70
  return self._get_field_handle(node.value, node.attr)
70
71
 
71
- def visit_Call(self, node: ast.Call) -> Optional[HandleBinding]:
72
+ def visit_Call(self, node: ast.Call) -> HandleBinding | None:
72
73
  if not isinstance(node.func, ast.Name):
73
74
  return self.generic_visit(node)
74
75
  if node.func.id == "get_field":
@@ -89,7 +90,7 @@ class VarRefCollector(ast.NodeVisitor):
89
90
 
90
91
  def _get_field_handle(
91
92
  self, subject: ast.expr, field: str
92
- ) -> Optional[FieldHandleBinding]:
93
+ ) -> FieldHandleBinding | None:
93
94
  with self.set_nested():
94
95
  base_handle = self.visit(subject)
95
96
  if base_handle is None:
@@ -104,7 +105,7 @@ class VarRefCollector(ast.NodeVisitor):
104
105
 
105
106
  def _get_subscript_handle(
106
107
  self, subject: ast.expr, subscript: ast.expr
107
- ) -> Optional[HandleBinding]:
108
+ ) -> HandleBinding | None:
108
109
  with self.set_in_subscript():
109
110
  self.visit(subscript)
110
111
  with self.set_nested():
@@ -138,7 +139,7 @@ class VarRefCollector(ast.NodeVisitor):
138
139
  self._add_handle(handle)
139
140
  return handle
140
141
 
141
- def visit_Name(self, node: ast.Name) -> Optional[HandleBinding]:
142
+ def visit_Name(self, node: ast.Name) -> HandleBinding | None:
142
143
  if not self._ignore_sympy_symbols and node.id in set(
143
144
  SYMPY_SUPPORTED_EXPRESSIONS
144
145
  ) | set(DEFAULT_SUPPORTED_FUNC_NAMES):
@@ -82,6 +82,9 @@ OPEN_LIBRARY_FUNCTIONS = [
82
82
  encode_in_angle,
83
83
  encode_on_bloch,
84
84
  _cond_phase_flip,
85
+ inplace_modular_multiply,
86
+ modular_multiply,
87
+ modular_add_qft_space,
85
88
  ]
86
89
 
87
90
  __all__ = [
@@ -101,14 +104,17 @@ __all__ = [
101
104
  "grover_search",
102
105
  "hadamard_transform",
103
106
  "inplace_c_modular_multiply",
107
+ "inplace_modular_multiply",
104
108
  "inplace_prepare_complex_amplitudes",
105
109
  "inplace_prepare_int",
106
110
  "inplace_prepare_sparse_amplitudes",
107
111
  "lcu",
108
112
  "lcu_pauli",
109
113
  "linear_pauli_rotations",
114
+ "modular_add_qft_space",
110
115
  "modular_exp",
111
116
  "modular_increment",
117
+ "modular_multiply",
112
118
  "multiswap",
113
119
  "phase_oracle",
114
120
  "prepare_basis_state",
@@ -10,8 +10,8 @@ from classiq.qmod.builtins.operations import (
10
10
  repeat,
11
11
  within_apply,
12
12
  )
13
- from classiq.qmod.qfunc import qfunc
14
- from classiq.qmod.qmod_variable import Const, Permutable, QArray, QBit, QNum
13
+ from classiq.qmod.qfunc import qfunc, qperm
14
+ from classiq.qmod.qmod_variable import Const, QArray, QBit, QNum
15
15
  from classiq.qmod.symbolic import pi
16
16
  from classiq.qmod.utilities import suppress_return_value
17
17
 
@@ -27,8 +27,8 @@ def _qct_d_operator(x: Const[QNum], q: QBit) -> None:
27
27
  control(x == 0, lambda: invert(lambda: _b_operator(q)))
28
28
 
29
29
 
30
- @qfunc
31
- def _qct_pi_operator(x: Permutable[QNum], q: Const[QBit]) -> None:
30
+ @qperm
31
+ def _qct_pi_operator(x: QNum, q: Const[QBit]) -> None:
32
32
  control(
33
33
  q == 1,
34
34
  lambda: [
@@ -125,7 +125,7 @@ def qct_qst_type1(x: QArray[QBit]) -> None:
125
125
  within_apply(lambda: _t_operator(x), lambda: qft(x))
126
126
 
127
127
 
128
- @qfunc(unchecked=["q"])
128
+ @qfunc(disable_const_checks=["q"])
129
129
  def qct_qst_type2(x: QArray[QBit], q: Const[QBit]) -> None:
130
130
  """
131
131
  [Qmod Classiq-library function]
@@ -8,23 +8,21 @@ from classiq.qmod.builtins.operations import (
8
8
  power,
9
9
  within_apply,
10
10
  )
11
- from classiq.qmod.qfunc import qfunc
11
+ from classiq.qmod.qfunc import qfunc, qperm
12
12
  from classiq.qmod.qmod_parameter import CInt
13
- from classiq.qmod.qmod_variable import Const, Permutable, QArray, QBit, QNum
14
- from classiq.qmod.quantum_callable import QCallable
13
+ from classiq.qmod.qmod_variable import Const, QArray, QBit, QNum
14
+ from classiq.qmod.quantum_callable import QCallable, QPerm
15
15
  from classiq.qmod.symbolic import pi
16
16
 
17
17
 
18
- @qfunc(unchecked=["target"])
19
- def _cond_phase_flip(
20
- predicate: QCallable[Permutable[QBit]], target: Const[QBit]
21
- ) -> None:
18
+ @qperm(disable_perm_check=True, disable_const_checks=["target"])
19
+ def _cond_phase_flip(predicate: QPerm[QBit], target: Const[QBit]) -> None:
22
20
  within_apply(lambda: H(target), lambda: predicate(target))
23
21
 
24
22
 
25
- @qfunc
23
+ @qperm
26
24
  def phase_oracle(
27
- predicate: QCallable[Const[QArray[QBit]], Permutable[QBit]],
25
+ predicate: QPerm[Const[QArray[QBit]], QBit],
28
26
  target: Const[QArray[QBit]],
29
27
  ) -> None:
30
28
  """
@@ -55,7 +53,7 @@ def phase_oracle(
55
53
  )
56
54
 
57
55
 
58
- @qfunc(unchecked=["packed_vars"])
56
+ @qperm(disable_perm_check=True, disable_const_checks=["packed_vars"])
59
57
  def reflect_about_zero(packed_vars: Const[QArray[QBit]]) -> None:
60
58
  """
61
59
  [Qmod Classiq-library function]