classiq 0.86.1__py3-none-any.whl → 0.88.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 (131) hide show
  1. classiq/__init__.py +2 -0
  2. classiq/applications/__init__.py +1 -2
  3. classiq/applications/chemistry/hartree_fock.py +5 -1
  4. classiq/applications/chemistry/op_utils.py +2 -2
  5. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +1 -1
  6. classiq/applications/combinatorial_helpers/encoding_mapping.py +11 -15
  7. classiq/applications/combinatorial_helpers/encoding_utils.py +6 -6
  8. classiq/applications/combinatorial_helpers/memory.py +4 -4
  9. classiq/applications/combinatorial_helpers/optimization_model.py +5 -5
  10. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +6 -10
  11. classiq/applications/combinatorial_helpers/pyomo_utils.py +27 -26
  12. classiq/applications/combinatorial_helpers/sympy_utils.py +2 -2
  13. classiq/applications/combinatorial_helpers/transformations/encoding.py +4 -6
  14. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +4 -4
  15. classiq/applications/combinatorial_helpers/transformations/ising_converter.py +2 -2
  16. classiq/applications/combinatorial_helpers/transformations/penalty_support.py +3 -3
  17. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -0
  18. classiq/applications/hamiltonian/pauli_decomposition.py +34 -2
  19. classiq/evaluators/argument_types.py +15 -6
  20. classiq/evaluators/parameter_types.py +43 -39
  21. classiq/evaluators/qmod_annotated_expression.py +117 -17
  22. classiq/evaluators/qmod_expression_visitors/out_of_place_node_transformer.py +19 -0
  23. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +0 -5
  24. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +66 -16
  25. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +48 -26
  26. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +65 -72
  27. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +13 -6
  28. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +175 -28
  29. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +36 -19
  30. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +17 -5
  31. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +24 -2
  32. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +97 -0
  33. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +11 -26
  34. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +56 -0
  35. classiq/evaluators/qmod_node_evaluators/piecewise_evaluation.py +40 -0
  36. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +3 -4
  37. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +51 -24
  38. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +53 -9
  39. classiq/evaluators/qmod_node_evaluators/utils.py +28 -6
  40. classiq/evaluators/qmod_type_inference/classical_type_inference.py +188 -0
  41. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +330 -0
  42. classiq/evaluators/quantum_type_utils.py +0 -131
  43. classiq/evaluators/type_type_match.py +1 -1
  44. classiq/execution/execution_session.py +18 -3
  45. classiq/execution/qnn.py +4 -1
  46. classiq/execution/user_budgets.py +1 -1
  47. classiq/interface/_version.py +1 -1
  48. classiq/interface/backend/backend_preferences.py +10 -30
  49. classiq/interface/backend/quantum_backend_providers.py +63 -52
  50. classiq/interface/execution/primitives.py +1 -0
  51. classiq/interface/generator/application_apis/__init__.py +0 -1
  52. classiq/interface/generator/arith/binary_ops.py +107 -115
  53. classiq/interface/generator/arith/extremum_operations.py +33 -45
  54. classiq/interface/generator/arith/number_utils.py +4 -1
  55. classiq/interface/generator/circuit_code/types_and_constants.py +0 -9
  56. classiq/interface/generator/compiler_keywords.py +2 -0
  57. classiq/interface/generator/expressions/atomic_expression_functions.py +0 -2
  58. classiq/interface/generator/function_param_list.py +129 -5
  59. classiq/interface/generator/functions/classical_type.py +67 -2
  60. classiq/interface/generator/functions/qmod_python_interface.py +15 -0
  61. classiq/interface/generator/functions/type_name.py +12 -0
  62. classiq/interface/generator/model/preferences/preferences.py +1 -17
  63. classiq/interface/generator/quantum_program.py +1 -13
  64. classiq/interface/generator/transpiler_basis_gates.py +5 -1
  65. classiq/interface/generator/types/builtin_enum_declarations.py +0 -8
  66. classiq/interface/helpers/model_normalizer.py +2 -2
  67. classiq/interface/helpers/text_utils.py +7 -2
  68. classiq/interface/interface_version.py +1 -1
  69. classiq/interface/model/classical_if.py +48 -0
  70. classiq/interface/model/classical_parameter_declaration.py +4 -0
  71. classiq/interface/model/handle_binding.py +28 -16
  72. classiq/interface/model/port_declaration.py +12 -0
  73. classiq/interface/model/quantum_function_declaration.py +12 -0
  74. classiq/interface/model/quantum_type.py +117 -2
  75. classiq/interface/pretty_print/expression_to_qmod.py +7 -8
  76. classiq/interface/pyomo_extension/__init__.py +0 -4
  77. classiq/interface/pyomo_extension/pyomo_sympy_bimap.py +2 -2
  78. classiq/model_expansions/arithmetic.py +43 -1
  79. classiq/model_expansions/arithmetic_compute_result_attrs.py +255 -0
  80. classiq/model_expansions/capturing/captured_vars.py +2 -5
  81. classiq/model_expansions/quantum_operations/allocate.py +23 -16
  82. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -3
  83. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -71
  84. classiq/model_expansions/quantum_operations/bind.py +15 -7
  85. classiq/model_expansions/quantum_operations/call_emitter.py +2 -10
  86. classiq/model_expansions/quantum_operations/classical_var_emitter.py +6 -0
  87. classiq/model_expansions/quantum_operations/handle_evaluator.py +2 -8
  88. classiq/open_library/functions/__init__.py +4 -0
  89. classiq/open_library/functions/lcu.py +117 -0
  90. classiq/open_library/functions/state_preparation.py +47 -5
  91. classiq/qmod/builtins/__init__.py +0 -3
  92. classiq/qmod/builtins/classical_functions.py +0 -28
  93. classiq/qmod/builtins/enums.py +26 -20
  94. classiq/qmod/builtins/functions/__init__.py +0 -5
  95. classiq/qmod/builtins/operations.py +142 -0
  96. classiq/qmod/builtins/structs.py +33 -29
  97. classiq/qmod/native/pretty_printer.py +1 -1
  98. classiq/qmod/pretty_print/expression_to_python.py +1 -6
  99. classiq/qmod/pretty_print/pretty_printer.py +4 -1
  100. classiq/qmod/qmod_variable.py +94 -2
  101. classiq/qmod/semantics/annotation/call_annotation.py +4 -2
  102. classiq/qmod/semantics/annotation/qstruct_annotator.py +20 -5
  103. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/METADATA +5 -5
  104. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/RECORD +106 -124
  105. classiq/applications/finance/__init__.py +0 -15
  106. classiq/interface/finance/finance_modelling_params.py +0 -11
  107. classiq/interface/finance/function_input.py +0 -102
  108. classiq/interface/finance/gaussian_model_input.py +0 -50
  109. classiq/interface/finance/log_normal_model_input.py +0 -40
  110. classiq/interface/finance/model_input.py +0 -22
  111. classiq/interface/generator/amplitude_estimation.py +0 -34
  112. classiq/interface/generator/application_apis/finance_declarations.py +0 -108
  113. classiq/interface/generator/expressions/enums/__init__.py +0 -0
  114. classiq/interface/generator/expressions/enums/finance_functions.py +0 -12
  115. classiq/interface/generator/finance.py +0 -107
  116. classiq/interface/generator/function_param_list_without_self_reference.py +0 -160
  117. classiq/interface/generator/grover_diffuser.py +0 -93
  118. classiq/interface/generator/grover_operator.py +0 -106
  119. classiq/interface/generator/oracles/__init__.py +0 -3
  120. classiq/interface/generator/oracles/arithmetic_oracle.py +0 -82
  121. classiq/interface/generator/oracles/custom_oracle.py +0 -65
  122. classiq/interface/generator/oracles/oracle_abc.py +0 -76
  123. classiq/interface/generator/oracles/oracle_function_param_list.py +0 -6
  124. classiq/interface/generator/piecewise_linear_amplitude_loading.py +0 -165
  125. classiq/interface/generator/qpe.py +0 -169
  126. classiq/interface/grover/__init__.py +0 -0
  127. classiq/interface/grover/grover_modelling_params.py +0 -13
  128. classiq/model_expansions/transformers/var_splitter.py +0 -224
  129. classiq/qmod/builtins/functions/finance.py +0 -34
  130. /classiq/{interface/finance → evaluators/qmod_type_inference}/__init__.py +0 -0
  131. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/WHEEL +0 -0
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Union
3
3
  import sympy
4
4
 
5
5
  from classiq.interface.debug_info.debug_info import FunctionDebugInfo
6
- from classiq.interface.exceptions import ClassiqValueError
6
+ from classiq.interface.exceptions import ClassiqExpansionError, ClassiqValueError
7
7
  from classiq.interface.generator.expressions.expression import Expression
8
8
  from classiq.interface.model.allocate import Allocate
9
9
  from classiq.interface.model.handle_binding import NestedHandleBinding
@@ -12,7 +12,9 @@ from classiq.interface.model.quantum_type import (
12
12
  QuantumNumeric,
13
13
  )
14
14
 
15
- from classiq.evaluators.quantum_type_utils import copy_type_information
15
+ from classiq.evaluators.qmod_type_inference.quantum_type_inference import (
16
+ inject_quantum_type_attributes_inplace,
17
+ )
16
18
  from classiq.model_expansions.quantum_operations.emitter import Emitter
17
19
  from classiq.model_expansions.scope import QuantumSymbol
18
20
 
@@ -76,7 +78,7 @@ class AllocateEmitter(Emitter[Allocate]):
76
78
  target: QuantumSymbol,
77
79
  op_update_dict: dict[str, Expression],
78
80
  ) -> None:
79
- if target.quantum_type.is_evaluated:
81
+ if target.quantum_type.has_size_in_bits:
80
82
  expr = str(target.quantum_type.size_in_bits)
81
83
  elif self._allow_symbolic_attrs:
82
84
  expr = f"{target.handle}.size"
@@ -95,11 +97,14 @@ class AllocateEmitter(Emitter[Allocate]):
95
97
  size_value = self._interpret_size(size, str(target.handle))
96
98
  op_update_dict["size"] = Expression(expr=str(size_value))
97
99
 
98
- if not isinstance(size_value, sympy.Basic):
99
- copy_type_information(
100
- QuantumBitvector(length=op_update_dict["size"]),
101
- target.quantum_type,
102
- str(target.handle),
100
+ if not isinstance(
101
+ size_value, sympy.Basic
102
+ ) and not inject_quantum_type_attributes_inplace(
103
+ QuantumBitvector(length=op_update_dict["size"]), target.quantum_type
104
+ ):
105
+ raise ClassiqExpansionError(
106
+ f"Cannot allocate {op_update_dict['size']} qubits for variable "
107
+ f"{str(target)!r} of type {target.quantum_type.qmod_type_name}"
103
108
  )
104
109
 
105
110
  def _handle_with_numeric_attrs(
@@ -127,15 +132,17 @@ class AllocateEmitter(Emitter[Allocate]):
127
132
  isinstance(size_value, sympy.Basic)
128
133
  or isinstance(is_signed_value, sympy.Basic)
129
134
  or isinstance(fraction_digits_value, sympy.Basic)
135
+ ) and not inject_quantum_type_attributes_inplace(
136
+ QuantumNumeric(
137
+ size=op_update_dict["size"],
138
+ is_signed=op_update_dict["is_signed"],
139
+ fraction_digits=op_update_dict["fraction_digits"],
140
+ ),
141
+ target.quantum_type,
130
142
  ):
131
- copy_type_information(
132
- QuantumNumeric(
133
- size=op_update_dict["size"],
134
- is_signed=op_update_dict["is_signed"],
135
- fraction_digits=op_update_dict["fraction_digits"],
136
- ),
137
- target.quantum_type,
138
- var_name,
143
+ raise ClassiqExpansionError(
144
+ f"Cannot allocate {op_update_dict['size']} qubits for variable "
145
+ f"{var_name!r} of type {target.quantum_type.qmod_type_name}"
139
146
  )
140
147
 
141
148
  def _interpret_size(
@@ -44,9 +44,7 @@ def convert_inplace_op_bool_expression(
44
44
  if not is_bool(op.expression.expr):
45
45
  return
46
46
  _validate_target_type(target, op.expression.expr, op.operation_kind)
47
- op.expression = op.expression.model_copy(
48
- update=dict(expr="1" if op.expression.expr == "True" else "0")
49
- )
47
+ op.expression = Expression(expr="1" if op.expression.expr == "True" else "0")
50
48
 
51
49
 
52
50
  def _supported_types() -> tuple[str, ...]:
@@ -27,7 +27,9 @@ from classiq.interface.model.variable_declaration_statement import (
27
27
  )
28
28
  from classiq.interface.model.within_apply_operation import WithinApply
29
29
 
30
- from classiq.evaluators.quantum_type_utils import copy_type_information
30
+ from classiq.evaluators.qmod_type_inference.quantum_type_inference import (
31
+ inject_quantum_type_attributes_inplace,
32
+ )
31
33
  from classiq.model_expansions.quantum_operations.arithmetic.explicit_boolean_expressions import (
32
34
  convert_assignment_bool_expression,
33
35
  validate_assignment_bool_expression,
@@ -67,30 +69,38 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
67
69
  )
68
70
  convert_assignment_bool_expression(op)
69
71
 
70
- inferred_result_type = self._infer_result_type(op)
71
- if inferred_result_type is None:
72
+ expression_type = self._infer_expression_type(op)
73
+ if expression_type is None:
72
74
  return False
73
75
 
74
- if not isinstance(result_type, QuantumNumeric):
75
- copy_type_information(
76
- inferred_result_type, result_symbol.quantum_type, str(op.result_var)
77
- )
76
+ if not result_type.is_instantiated:
77
+ if not inject_quantum_type_attributes_inplace(expression_type, result_type):
78
+ raise ClassiqExpansionError(
79
+ f"Cannot assign expression of type "
80
+ f"{expression_type.qmod_type_name} to variable "
81
+ f"{str(result_symbol)!r} of type "
82
+ f"{result_symbol.quantum_type.qmod_type_name}"
83
+ )
78
84
  return False
79
85
 
80
- self._copy_numeric_attributes(result_type, inferred_result_type)
81
- if self._same_numeric_attributes(result_type, inferred_result_type):
86
+ if isinstance(result_type, QuantumNumeric) and isinstance(
87
+ expression_type, QuantumNumeric
88
+ ):
89
+ result_type.set_bounds(expression_type.get_bounds())
90
+ if self._same_numeric_attributes(result_type, expression_type):
82
91
  return False
83
-
84
92
  self._validate_declared_attributes(
85
- result_type, inferred_result_type, str(op.result_var)
93
+ result_type, expression_type, str(op.result_var)
86
94
  )
87
- if self._replace_assignment_if_needed:
88
- self._assign_to_inferred_var_and_bind(op, result_type, inferred_result_type)
89
- return True
90
- else:
95
+ if not self._replace_assignment_if_needed:
91
96
  return False
92
97
 
93
- def _infer_result_type(self, op: ArithmeticOperation) -> Optional[QuantumNumeric]:
98
+ self._assign_to_inferred_var_and_bind(op, result_type, expression_type)
99
+ return True
100
+
101
+ def _infer_expression_type(
102
+ self, op: ArithmeticOperation
103
+ ) -> Optional[QuantumNumeric]:
94
104
  expr = self._evaluate_expression(op.expression)
95
105
  if len(self._get_classical_vars_in_expression(expr)):
96
106
  return None
@@ -114,96 +124,72 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
114
124
  self._machine_precision,
115
125
  )
116
126
 
117
- @staticmethod
118
- def _copy_numeric_attributes(
119
- result_type: QuantumNumeric, inferred_result_type: QuantumNumeric
120
- ) -> None:
121
- if not result_type.has_size_in_bits:
122
- result_type.size = Expression(expr=str(inferred_result_type.size_in_bits))
123
- if not result_type.has_sign:
124
- result_type.is_signed = Expression(
125
- expr=str(inferred_result_type.sign_value)
126
- )
127
- if not result_type.has_fraction_digits:
128
- result_type.fraction_digits = Expression(
129
- expr=str(inferred_result_type.fraction_digits_value)
130
- )
131
- result_type.set_bounds(inferred_result_type.get_bounds())
132
-
133
127
  @staticmethod
134
128
  def _same_numeric_attributes(
135
- result_type: QuantumNumeric, inferred_result_type: QuantumNumeric
129
+ result_type: QuantumNumeric, expression_type: QuantumNumeric
136
130
  ) -> bool:
137
131
  return (
138
- result_type.size_in_bits == inferred_result_type.size_in_bits
139
- and result_type.sign_value == inferred_result_type.sign_value
132
+ result_type.size_in_bits == expression_type.size_in_bits
133
+ and result_type.sign_value == expression_type.sign_value
140
134
  and result_type.fraction_digits_value
141
- == inferred_result_type.fraction_digits_value
135
+ == expression_type.fraction_digits_value
142
136
  )
143
137
 
144
138
  @classmethod
145
139
  def _validate_declared_attributes(
146
- cls, result_type: QuantumNumeric, inferred_result_type: QuantumNumeric, var: str
140
+ cls, result_type: QuantumNumeric, expression_type: QuantumNumeric, var: str
147
141
  ) -> None:
148
142
  result_size, result_sign, result_fractions = (
149
143
  result_type.size_in_bits,
150
144
  result_type.sign_value,
151
145
  result_type.fraction_digits_value,
152
146
  )
153
- inferred_size, inferred_sign, inferred_fractions = (
154
- inferred_result_type.size_in_bits,
155
- inferred_result_type.sign_value,
156
- inferred_result_type.fraction_digits_value,
147
+ expression_size, expression_sign, expression_fractions = (
148
+ expression_type.size_in_bits,
149
+ expression_type.sign_value,
150
+ expression_type.fraction_digits_value,
157
151
  )
158
152
  result_integers = result_size - result_fractions
159
- inferred_integers = inferred_size - inferred_fractions
153
+ expression_integers = expression_size - expression_fractions
160
154
 
161
155
  if (
162
- (result_integers < inferred_integers)
163
- or (result_fractions < inferred_fractions)
164
- or (not result_sign and inferred_sign)
156
+ (result_integers < expression_integers)
157
+ or (result_fractions < expression_fractions)
158
+ or (not result_sign and expression_sign)
165
159
  or (
166
160
  result_sign
167
- and not inferred_sign
168
- and result_integers == inferred_integers
161
+ and not expression_sign
162
+ and result_integers == expression_integers
169
163
  )
170
164
  ):
171
165
  if (
172
166
  not result_sign
173
167
  and result_fractions == 0
174
- and not inferred_sign
175
- and inferred_fractions == 0
168
+ and not expression_sign
169
+ and expression_fractions == 0
176
170
  ):
177
171
  result_size_str = f"size {result_size}"
178
- inferred_size_str = f"size {inferred_size}"
172
+ expression_size_str = f"size {expression_size}"
179
173
  hint = f"Hint: increase the size in the declaration of {var!r} or omit it to enable automatic inference."
180
174
  else:
181
175
  result_size_str = f"size {result_size}, {'signed' if result_sign else 'unsigned'}, and {result_fractions} fraction digits"
182
- inferred_size_str = f"size {inferred_size}, {'signed' if inferred_sign else 'unsigned'}, and {inferred_fractions} fraction digits"
176
+ expression_size_str = f"size {expression_size}, {'signed' if expression_sign else 'unsigned'}, and {expression_fractions} fraction digits"
183
177
  hint = f"Hint: omit the numeric attributes from the declaration of {var!r} to enable automatic inference."
184
178
  raise ClassiqExpansionError(
185
- f"Cannot assign an expression with inferred {inferred_size_str} to variable {var!r} with declared {result_size_str}. {hint}"
179
+ f"Cannot assign an expression with inferred {expression_size_str} to variable {var!r} with declared {result_size_str}. {hint}"
186
180
  )
187
181
 
188
- @staticmethod
189
- def _craft_size_string(size: int, is_signed: bool, fraction_digits: int) -> str:
190
- extra = (
191
- f", with {fraction_digits} fraction digits" if fraction_digits > 0 else ""
192
- )
193
- return f"{size} ({'signed' if is_signed else 'unsigned'}{extra})"
194
-
195
182
  def _assign_to_inferred_var_and_bind(
196
183
  self,
197
184
  op: ArithmeticOperation,
198
185
  result_type: QuantumNumeric,
199
- inferred_result_type: QuantumNumeric,
186
+ expression_type: QuantumNumeric,
200
187
  ) -> None:
201
188
  stmts: StatementBlock = []
202
189
  handles: list[HandleBinding] = []
203
190
 
204
191
  extra_fraction_digits = (
205
- result_type.fraction_digits_value
206
- - inferred_result_type.fraction_digits_value
192
+ result_type.fraction_digits_value - expression_type.fraction_digits_value
207
193
  )
208
194
  if extra_fraction_digits > 0:
209
195
  handles.append(
@@ -216,7 +202,7 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
216
202
  inferred_result_handle = HandleBinding(name=inferred_result_name)
217
203
  stmts.append(
218
204
  VariableDeclarationStatement(
219
- name=inferred_result_name, qmod_type=inferred_result_type
205
+ name=inferred_result_name, qmod_type=expression_type
220
206
  )
221
207
  )
222
208
  handles.append(inferred_result_handle)
@@ -228,8 +214,7 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
228
214
  result_type.size_in_bits - result_type.fraction_digits_value
229
215
  )
230
216
  inferred_result_integer_size = (
231
- inferred_result_type.size_in_bits
232
- - inferred_result_type.fraction_digits_value
217
+ expression_type.size_in_bits - expression_type.fraction_digits_value
233
218
  )
234
219
  extra_integers = result_integer_size - inferred_result_integer_size
235
220
  if extra_integers > 0:
@@ -239,17 +224,13 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
239
224
 
240
225
  stmts.append(BindOperation(in_handles=handles, out_handles=[op.result_var]))
241
226
 
242
- if (
243
- result_type.sign_value
244
- and inferred_result_type.sign_value
245
- and extra_integers > 0
246
- ):
227
+ if result_type.sign_value and expression_type.sign_value and extra_integers > 0:
247
228
  sign_idx = result_type.size_in_bits - extra_integers - 1
248
229
  self._sign_extension(
249
230
  op.result_var, sign_idx, result_type.size_in_bits, stmts
250
231
  )
251
232
 
252
- if (inferred_bounds := inferred_result_type.get_bounds()) is not None:
233
+ if (inferred_bounds := expression_type.get_bounds()) is not None:
253
234
  lower_bound = Expression(expr=str(inferred_bounds[0]))
254
235
  upper_bound = Expression(expr=str(inferred_bounds[1]))
255
236
  else:
@@ -5,14 +5,17 @@ from classiq.interface.exceptions import (
5
5
  ClassiqExpansionError,
6
6
  ClassiqInternalExpansionError,
7
7
  )
8
+ from classiq.interface.generator.expressions.expression import Expression
8
9
  from classiq.interface.model.bind_operation import BindOperation
9
- from classiq.interface.model.quantum_type import QuantumNumeric
10
+ from classiq.interface.model.quantum_type import QuantumBitvector, QuantumNumeric
10
11
 
11
12
  from classiq.evaluators.parameter_types import (
12
13
  evaluate_types_in_quantum_symbols,
13
14
  )
15
+ from classiq.evaluators.qmod_type_inference.quantum_type_inference import (
16
+ inject_quantum_type_attributes_inplace,
17
+ )
14
18
  from classiq.evaluators.quantum_type_utils import (
15
- set_size,
16
19
  validate_bind_targets,
17
20
  )
18
21
  from classiq.model_expansions.quantum_operations.emitter import Emitter
@@ -134,11 +137,16 @@ class BindEmitter(Emitter[BindOperation]):
134
137
  if output.quantum_type.has_size_in_bits
135
138
  )
136
139
  if len(unsized_outputs) == 1:
137
- set_size(
138
- unsized_outputs[0].quantum_type,
139
- input_size - output_size,
140
- str(unsized_outputs[0].handle),
141
- )
140
+ unsized_type = unsized_outputs[0].quantum_type
141
+ new_size = input_size - output_size
142
+ if not inject_quantum_type_attributes_inplace(
143
+ QuantumBitvector(length=Expression(expr=str(new_size))), unsized_type
144
+ ):
145
+ raise ClassiqExpansionError(
146
+ f"Cannot bind {new_size} qubits to variable "
147
+ f"{str(unsized_outputs[0])!r} of type "
148
+ f"{unsized_type.qmod_type_name}"
149
+ )
142
150
  elif input_size != output_size:
143
151
  raise ClassiqExpansionError(
144
152
  f"The total size for the input and output of the bind operation must be the same. The in size is {input_size} and the out size is {output_size}"
@@ -1,7 +1,6 @@
1
1
  from collections.abc import Sequence
2
2
  from itertools import chain, combinations
3
3
  from typing import (
4
- TYPE_CHECKING,
5
4
  Any,
6
5
  Generic,
7
6
  Optional,
@@ -80,18 +79,15 @@ from classiq.model_expansions.scope import (
80
79
  QuantumVariable,
81
80
  Scope,
82
81
  )
82
+ from classiq.model_expansions.transformers.model_renamer import ModelRenamer
83
83
  from classiq.model_expansions.transformers.type_modifier_inference import (
84
84
  TypeModifierValidation,
85
85
  )
86
- from classiq.model_expansions.transformers.var_splitter import VarSplitter
87
86
  from classiq.qmod.pretty_print.expression_to_python import transform_expression
88
87
  from classiq.qmod.semantics.validation.signature_validation import (
89
88
  validate_function_signature,
90
89
  )
91
90
 
92
- if TYPE_CHECKING:
93
- from classiq.model_expansions.interpreters.base_interpreter import BaseInterpreter
94
-
95
91
 
96
92
  def _validate_cloning(evaluated_args: list[Evaluated]) -> None:
97
93
  handles = chain.from_iterable(
@@ -145,11 +141,7 @@ def _validate_gen_args(
145
141
  )
146
142
 
147
143
 
148
- class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSplitter):
149
- def __init__(self, interpreter: "BaseInterpreter") -> None:
150
- Emitter.__init__(self, interpreter)
151
- VarSplitter.__init__(self, interpreter._builder.current_scope)
152
-
144
+ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], ModelRenamer):
153
145
  @staticmethod
154
146
  def _should_wrap(body: Sequence[QuantumStatement]) -> bool:
155
147
  # This protects shadowing of captured variables (i.e, bad user code) by wrapping the body in a function
@@ -1,3 +1,6 @@
1
+ import re
2
+
3
+ from classiq.interface.model.handle_binding import HandleBinding
1
4
  from classiq.interface.model.quantum_expressions.arithmetic_operation import (
2
5
  ArithmeticOperation,
3
6
  )
@@ -12,5 +15,8 @@ class ClassicalVarEmitter(Emitter[ArithmeticOperation]):
12
15
  if not isinstance(result_symbol, ClassicalSymbol):
13
16
  return False
14
17
  op._classical_assignment = True
18
+ match = re.search(r"measure\((.*?)\)", op.expression.expr)
19
+ if match is not None:
20
+ op.set_var_handles([HandleBinding(name=match.group(1))])
15
21
  self.emit_statement(op)
16
22
  return True
@@ -19,11 +19,5 @@ class HandleEvaluator(Emitter[QuantumOperation]):
19
19
  if not isinstance(handle, HandleBinding):
20
20
  return False
21
21
  evaluated_handle = self._interpreter.evaluate(handle).value.handle.collapse()
22
- if handle == evaluated_handle:
23
- return False
24
- op = op.model_copy(
25
- update={self._handle_name: evaluated_handle, "back_ref": op.uuid}
26
- )
27
- self._interpreter.add_to_debug_info(op)
28
- self._interpreter.emit(op)
29
- return True
22
+ setattr(op, self._handle_name, evaluated_handle)
23
+ return False
@@ -8,6 +8,7 @@ from .discrete_sine_cosine_transform import _qct_d_operator, _qct_pi_operator
8
8
  from .grover import *
9
9
  from .grover import _cond_phase_flip
10
10
  from .hea import *
11
+ from .lcu import *
11
12
  from .linear_pauli_rotation import *
12
13
  from .linear_pauli_rotation import _single_pauli
13
14
  from .lookup_table import span_lookup_table
@@ -100,6 +101,8 @@ __all__ = [
100
101
  "inplace_c_modular_multiply",
101
102
  "inplace_prepare_complex_amplitudes",
102
103
  "inplace_prepare_int",
104
+ "lcu",
105
+ "lcu_pauli",
103
106
  "linear_pauli_rotations",
104
107
  "modular_exp",
105
108
  "modular_increment",
@@ -113,6 +116,7 @@ __all__ = [
113
116
  "prepare_exponential_state",
114
117
  "prepare_ghz_state",
115
118
  "prepare_int",
119
+ "prepare_linear_amplitudes",
116
120
  "prepare_uniform_interval_state",
117
121
  "prepare_uniform_trimmed_state",
118
122
  "projector_controlled_double_phase",
@@ -0,0 +1,117 @@
1
+ from typing import Literal
2
+
3
+ import numpy as np
4
+
5
+ from classiq.open_library.functions.state_preparation import (
6
+ apply_phase_table,
7
+ )
8
+ from classiq.open_library.functions.utility_functions import switch
9
+ from classiq.qmod.builtins.functions import IDENTITY, X, Y, Z, inplace_prepare_state
10
+ from classiq.qmod.builtins.operations import (
11
+ control,
12
+ repeat,
13
+ within_apply,
14
+ )
15
+ from classiq.qmod.builtins.structs import IndexedPauli, SparsePauliOp
16
+ from classiq.qmod.cparam import CArray
17
+ from classiq.qmod.qfunc import qfunc
18
+ from classiq.qmod.qmod_variable import QArray, QBit, QNum
19
+ from classiq.qmod.quantum_callable import QCallableList
20
+
21
+
22
+ @qfunc
23
+ def apply_pauli_term(pauli_string: CArray[IndexedPauli], x: QArray[QBit]) -> None:
24
+ repeat(
25
+ count=pauli_string.len,
26
+ iteration=lambda i: switch(
27
+ pauli_string[i].pauli,
28
+ [
29
+ lambda: IDENTITY(x[pauli_string[i].index]),
30
+ lambda: X(x[pauli_string[i].index]),
31
+ lambda: Y(x[pauli_string[i].index]),
32
+ lambda: Z(x[pauli_string[i].index]),
33
+ ],
34
+ ),
35
+ )
36
+
37
+
38
+ @qfunc
39
+ def lcu(
40
+ coefficients: list[float],
41
+ unitaries: QCallableList,
42
+ block: QNum[Literal["max(ceiling(log(coefficients.len, 2)), 1)"]],
43
+ ) -> None:
44
+ """
45
+ [Qmod Classiq-library function]
46
+
47
+ Implements a general linear combination of unitaries (LCU) procedure. The algorithm prepares a superposition
48
+ over the `unitaries` according to `magnitudes`, and then conditionally applies each unitary controlled by the `block`.
49
+
50
+ The operation is of the form:
51
+
52
+ $$\\sum_j \\alpha_j U_j$$
53
+
54
+ where $U_j$ is a unitary operation applied to `data`.
55
+
56
+ Args:
57
+ coefficients: L1-normalized array of $\\{ \\alpha_j \\}$ of the LCU coefficients.
58
+ unitaries: A list of quantum callable functions to be applied conditionally.
59
+ block: Quantum variable that holds the superposition index used for conditional application of each unitary.
60
+ """
61
+ coefficients = coefficients + [0] * (
62
+ 2**block.size - len(coefficients) # type:ignore[operator]
63
+ )
64
+ magnitudes = [np.abs(c) for c in coefficients]
65
+ magnitudes = (np.array(magnitudes) / np.sum(magnitudes)).tolist()
66
+ phases = [np.angle(complex(c)) for c in coefficients]
67
+
68
+ within_apply(
69
+ lambda: inplace_prepare_state(magnitudes, 0, block),
70
+ lambda: [
71
+ repeat(
72
+ count=unitaries.len,
73
+ iteration=lambda i: control(block == i, lambda: unitaries[i]()),
74
+ ),
75
+ # TODO: replace to sparse constant phase
76
+ apply_phase_table(phases, block),
77
+ ],
78
+ )
79
+
80
+
81
+ @qfunc
82
+ def lcu_pauli(
83
+ operator: SparsePauliOp,
84
+ data: QArray[QBit, Literal["operator.num_qubits"]],
85
+ block: QNum[Literal["max(ceiling(log(operator.terms.len, 2)), 1)"]],
86
+ ) -> None:
87
+ """
88
+ [Qmod Classiq-library function]
89
+
90
+ Applies a linear combination of unitaries (LCU) where each unitary is a Pauli term,
91
+ represented as a tensor product of Pauli operators. The function prepares a superposition
92
+ over the unitaries according to the given magnitudes and phases, and applies the corresponding
93
+ Pauli operators conditionally.
94
+
95
+ This is useful for implementing Hamiltonian terms of the form:
96
+
97
+ $$H=\\sum_j \\alpha_j P_j$$
98
+
99
+ where $P_j$ is a tensor product of Pauli operators.
100
+
101
+ Args:
102
+ operator: Operator consists of pauli strings with their coefficients, represented in a sparse format.
103
+ data: Quantum Variable on which the Pauli operators act. Its size must match the number of qubits required by the Pauli operator.
104
+ block: Quantum variable that holds the superposition index used for conditional application of each term.
105
+ """
106
+ coefficients = [
107
+ operator.terms[i].coefficient
108
+ for i in range(operator.terms.len) # type:ignore[attr-defined]
109
+ ]
110
+ lcu(
111
+ coefficients,
112
+ [
113
+ lambda i=i: apply_pauli_term(operator.terms[i].paulis, data)
114
+ for i in range(operator.terms.len) # type:ignore[attr-defined]
115
+ ],
116
+ block,
117
+ )
@@ -13,8 +13,8 @@ from classiq.open_library.functions.utility_functions import (
13
13
  from classiq.qmod.builtins.functions import (
14
14
  CX,
15
15
  IDENTITY,
16
+ PHASE,
16
17
  RY,
17
- RZ,
18
18
  H,
19
19
  X,
20
20
  inplace_prepare_amplitudes,
@@ -324,7 +324,7 @@ def _classical_hadamard_transform(arr: list[float]) -> np.ndarray:
324
324
 
325
325
 
326
326
  @qfunc
327
- def _load_phases(
327
+ def apply_phase_table(
328
328
  phases: list[float],
329
329
  target: QArray[QBit, Literal["log(get_field(phases, 'len'), 2)"]],
330
330
  ) -> None:
@@ -333,10 +333,10 @@ def _load_phases(
333
333
  for i in range(1, len(alphas) - 1):
334
334
  gray = _graycode(i)
335
335
  next_gray = _graycode(i + 1)
336
- RZ(alphas[gray], target[_msb(gray)])
336
+ PHASE(alphas[gray], target[_msb(gray)])
337
337
  CX(target[_control_qubit(i)], target[_msb(next_gray)])
338
338
 
339
- RZ(alphas[_graycode(len(phases) - 1)], target[target.len - 1])
339
+ PHASE(alphas[_graycode(len(phases) - 1)], target[target.len - 1])
340
340
 
341
341
 
342
342
  @qfunc
@@ -358,7 +358,7 @@ def inplace_prepare_complex_amplitudes(
358
358
  target: The quantum variable to act upon.
359
359
  """
360
360
  inplace_prepare_amplitudes(magnitudes, 0, target)
361
- _load_phases(phases, target)
361
+ apply_phase_table(phases, target)
362
362
 
363
363
 
364
364
  @qfunc
@@ -469,3 +469,45 @@ def prepare_basis_state(state: list[bool], arr: Output[QArray]) -> None:
469
469
  for idx, value in enumerate(state):
470
470
  if value:
471
471
  X(arr[idx])
472
+
473
+
474
+ def linear_hadamard_walsh_coefficients(n: int) -> np.ndarray:
475
+ coeffs = np.zeros(n + 1)
476
+ coeffs[0] = 2 ** (n / 2) * ((2**n - 1) / 2)
477
+ for k in range(1, n + 1):
478
+ coeffs[k] = -(2 ** (k - 1 + n / 2) / 2)
479
+ return coeffs / np.linalg.norm(coeffs)
480
+
481
+
482
+ @qfunc
483
+ def _zero_ctrl_rot(ctrl: QNum, target: QBit, theta: CReal) -> None:
484
+ control(ctrl == 0, lambda: RY(theta, target))
485
+
486
+
487
+ @qfunc
488
+ def prepare_linear_amplitudes(x: QArray) -> None:
489
+ """ "
490
+ [Qmod Classiq-library function]
491
+
492
+ Initializes a quantum variable in a state with linear amplitudes:
493
+ $$|\\psi\rangle = \frac{1}{Z}\\sum_{x=0}^{2^n-1}{x|x\rangle}$$
494
+ Where $Z$ is a normalization constant.
495
+
496
+ Based on the paper https://quantum-journal.org/papers/q-2024-03-21-1297/pdf/
497
+
498
+ Args:
499
+ x: The quantum register to prepare.
500
+ """
501
+ coeffs = linear_hadamard_walsh_coefficients(x.size) # type: ignore[arg-type]
502
+ thetas = np.zeros(x.size + 1) # type: ignore[arg-type]
503
+ for i in range(x.size): # type: ignore[arg-type]
504
+ thetas[i] = 2 * np.arcsin(
505
+ coeffs[i + 1] / np.sqrt(1 - np.linalg.norm(coeffs[1 : i + 1]) ** 2)
506
+ )
507
+ for k in range(x.len):
508
+ if k == 0:
509
+ RY(thetas[k], x[k])
510
+ else:
511
+ _zero_ctrl_rot(x[0:k], x[k], thetas[k])
512
+
513
+ hadamard_transform(x)
@@ -1,5 +1,3 @@
1
- from classiq.interface.finance.function_input import FinanceFunctionInput
2
-
3
1
  from .classical_execution_primitives import * # noqa: F403
4
2
  from .classical_execution_primitives import (
5
3
  __all__ as _builtin_classical_execution_primitives,
@@ -17,7 +15,6 @@ from .operations import __all__ as _builtin_operations
17
15
  from .structs import * # noqa: F403
18
16
  from .structs import __all__ as _builtin_structs
19
17
 
20
- FinanceFunctionInput.model_rebuild()
21
18
  BUILTIN_CONSTANTS = [
22
19
  constant._get_constant_node()
23
20
  for constant in [