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,5 +1,5 @@
1
1
  from functools import singledispatchmethod
2
- from typing import Any
2
+ from typing import Any, Optional
3
3
 
4
4
  import numpy as np
5
5
  from numpy.random import permutation
@@ -61,7 +61,11 @@ from classiq.model_expansions.quantum_operations.allocate import AllocateEmitter
61
61
  from classiq.model_expansions.quantum_operations.assignment_result_processor import (
62
62
  AssignmentResultProcessor,
63
63
  )
64
- from classiq.model_expansions.quantum_operations.block_evaluator import BlockEvaluator
64
+ from classiq.model_expansions.quantum_operations.block_evaluator import (
65
+ BlockEvaluator,
66
+ IfElimination,
67
+ RepeatElimination,
68
+ )
65
69
  from classiq.model_expansions.quantum_operations.composite_emitter import (
66
70
  CompositeEmitter,
67
71
  )
@@ -100,9 +104,9 @@ class GenerativeInterpreter(BaseInterpreter):
100
104
  def infer_symbolic_parameters(
101
105
  self,
102
106
  functions: list[NativeFunctionDefinition],
103
- additional_signatures: (
104
- list[NamedParamsQuantumFunctionDeclaration] | None
105
- ) = None,
107
+ additional_signatures: Optional[
108
+ list[NamedParamsQuantumFunctionDeclaration]
109
+ ] = None,
106
110
  ) -> None:
107
111
  pass
108
112
 
@@ -152,10 +156,16 @@ class GenerativeInterpreter(BaseInterpreter):
152
156
  QuantumFunctionCallEmitter(self).emit(call)
153
157
 
154
158
  @emit.register
159
+ def _emit_allocate(self, allocate: Allocate) -> None:
160
+ return self.emit_allocate(allocate)
161
+
155
162
  def emit_allocate(self, allocate: Allocate) -> None:
156
163
  AllocateEmitter(self).emit(allocate)
157
164
 
158
165
  @emit.register
166
+ def _emit_bind(self, bind: BindOperation) -> None:
167
+ self.emit_bind(bind)
168
+
159
169
  def emit_bind(self, bind: BindOperation) -> None:
160
170
  BindEmitter(self).emit(bind)
161
171
 
@@ -210,6 +220,7 @@ class GenerativeInterpreter(BaseInterpreter):
210
220
  self,
211
221
  [
212
222
  ExpressionEvaluator(self, "condition"),
223
+ IfElimination(self),
213
224
  BlockEvaluator(
214
225
  self,
215
226
  CLASSICAL_IF_OPERATOR_NAME,
@@ -243,6 +254,7 @@ class GenerativeInterpreter(BaseInterpreter):
243
254
  self,
244
255
  [
245
256
  ExpressionEvaluator(self, "count"),
257
+ RepeatElimination(self),
246
258
  RepeatBlockEvaluator(self, REPEAT_OPERATOR_NAME, "body"),
247
259
  ],
248
260
  ).emit(repeat)
@@ -1,10 +1,13 @@
1
- from typing import TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, Optional
2
2
 
3
3
  import sympy
4
4
 
5
5
  from classiq.interface.debug_info.debug_info import FunctionDebugInfo
6
6
  from classiq.interface.exceptions import ClassiqValueError
7
7
  from classiq.interface.generator.expressions.expression import Expression
8
+ from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
9
+ AnyClassicalValue,
10
+ )
8
11
  from classiq.interface.model.allocate import Allocate
9
12
  from classiq.interface.model.handle_binding import NestedHandleBinding
10
13
  from classiq.interface.model.quantum_type import QuantumBitvector
@@ -46,7 +49,7 @@ class AllocateEmitter(Emitter[Allocate]):
46
49
  self.emit_statement(allocate)
47
50
  return True
48
51
 
49
- def _get_var_size(self, target: QuantumSymbol, size: Expression | None) -> str:
52
+ def _get_var_size(self, target: QuantumSymbol, size: Optional[Expression]) -> str:
50
53
  if size is None:
51
54
  if not target.quantum_type.is_evaluated:
52
55
  raise ClassiqValueError(
@@ -55,7 +58,9 @@ class AllocateEmitter(Emitter[Allocate]):
55
58
  return str(target.quantum_type.size_in_bits)
56
59
 
57
60
  size_value = self._interpreter.evaluate(size).value
58
- if self._allow_symbolic_size and isinstance(size_value, sympy.Basic):
61
+ if self._allow_symbolic_size and isinstance(
62
+ size_value, (sympy.Basic, AnyClassicalValue)
63
+ ):
59
64
  return str(size_value)
60
65
  if not isinstance(size_value, (int, float)):
61
66
  raise ClassiqValueError(
@@ -1,6 +1,14 @@
1
+ from typing import Optional
2
+
3
+ from classiq.interface.exceptions import ClassiqExpansionError
1
4
  from classiq.interface.generator.arith.arithmetic import compute_arithmetic_result_type
2
- from classiq.interface.generator.functions.port_declaration import (
3
- PortDeclarationDirection,
5
+ from classiq.interface.generator.expressions.expression import Expression
6
+ from classiq.interface.model.allocate import Allocate
7
+ from classiq.interface.model.bind_operation import BindOperation
8
+ from classiq.interface.model.handle_binding import (
9
+ ConcreteHandleBinding,
10
+ HandleBinding,
11
+ SubscriptHandleBinding,
4
12
  )
5
13
  from classiq.interface.model.quantum_expressions.arithmetic_operation import (
6
14
  ArithmeticOperation,
@@ -9,6 +17,12 @@ from classiq.interface.model.quantum_expressions.arithmetic_operation import (
9
17
  from classiq.interface.model.quantum_expressions.quantum_expression import (
10
18
  QuantumAssignmentOperation,
11
19
  )
20
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
21
+ from classiq.interface.model.quantum_type import QuantumBitvector, QuantumNumeric
22
+ from classiq.interface.model.variable_declaration_statement import (
23
+ VariableDeclarationStatement,
24
+ )
25
+ from classiq.interface.model.within_apply_operation import WithinApply
12
26
 
13
27
  from classiq.model_expansions.evaluators.quantum_type_utils import copy_type_information
14
28
  from classiq.model_expansions.quantum_operations.arithmetic.explicit_boolean_expressions import (
@@ -18,26 +32,49 @@ from classiq.model_expansions.quantum_operations.arithmetic.explicit_boolean_exp
18
32
  from classiq.model_expansions.quantum_operations.emitter import Emitter
19
33
  from classiq.model_expansions.scope import QuantumSymbol
20
34
  from classiq.model_expansions.transformers.ast_renamer import rename_variables
35
+ from classiq.qmod.builtins.functions.standard_gates import CX
21
36
 
22
37
 
23
38
  class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
24
39
  def emit(self, op: QuantumAssignmentOperation, /) -> bool:
25
- if (
40
+ if not (
26
41
  isinstance(op, ArithmeticOperation)
27
42
  and op.operation_kind == ArithmeticOperationKind.Assignment
28
43
  ):
29
- direction = PortDeclarationDirection.Output
30
- self._update_result_type(op)
31
- convert_assignment_bool_expression(op)
32
- else:
33
- direction = PortDeclarationDirection.Inout
34
- self._capture_handle(op.result_var, direction)
35
- return False
36
-
37
- def _update_result_type(self, op: ArithmeticOperation) -> None:
44
+ return False
45
+
46
+ result_symbol = self._interpreter.evaluate(op.result_var).as_type(QuantumSymbol)
47
+ result_type = result_symbol.quantum_type
48
+
49
+ validate_assignment_bool_expression(
50
+ result_symbol, op.expression.expr, op.operation_kind
51
+ )
52
+ convert_assignment_bool_expression(op)
53
+
54
+ inferred_result_type = self._infer_result_type(op)
55
+ if inferred_result_type is None:
56
+ return False
57
+
58
+ if not isinstance(result_type, QuantumNumeric):
59
+ copy_type_information(
60
+ inferred_result_type, result_symbol.quantum_type, str(op.result_var)
61
+ )
62
+ return False
63
+
64
+ self._copy_numeric_attributes(result_type, inferred_result_type)
65
+ if self._same_numeric_attributes(result_type, inferred_result_type):
66
+ return False
67
+
68
+ self._validate_declared_attributes(
69
+ result_type, inferred_result_type, str(op.result_var)
70
+ )
71
+ self._assign_to_inferred_var_and_bind(op, result_type, inferred_result_type)
72
+ return True
73
+
74
+ def _infer_result_type(self, op: ArithmeticOperation) -> Optional[QuantumNumeric]:
38
75
  expr = self._evaluate_expression(op.expression)
39
76
  if len(self._get_classical_vars_in_expression(expr)):
40
- return
77
+ return None
41
78
  symbols = self._get_symbols_in_expression(expr)
42
79
  expr_str = rename_variables(
43
80
  expr.expr,
@@ -48,16 +85,180 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
48
85
  expr_str = expr_str.replace(
49
86
  symbol.handle.qmod_expr, symbol.handle.identifier
50
87
  )
51
- result_type = compute_arithmetic_result_type(
88
+ return compute_arithmetic_result_type(
52
89
  expr_str,
53
90
  {symbol.handle.identifier: symbol.quantum_type for symbol in symbols},
54
91
  self._machine_precision,
55
92
  )
56
- result_symbol = self._interpreter.evaluate(op.result_var).as_type(QuantumSymbol)
57
93
 
58
- validate_assignment_bool_expression(
59
- result_symbol, op.expression.expr, op.operation_kind
60
- ) # must be here, otherwise copy_type_information will throw a non-indicative error
61
- copy_type_information(
62
- result_type, result_symbol.quantum_type, str(op.result_var)
94
+ @staticmethod
95
+ def _copy_numeric_attributes(
96
+ result_type: QuantumNumeric, inferred_result_type: QuantumNumeric
97
+ ) -> None:
98
+ if not result_type.has_size_in_bits:
99
+ result_type.size = Expression(expr=str(inferred_result_type.size_in_bits))
100
+ if not result_type.has_sign:
101
+ result_type.is_signed = Expression(
102
+ expr=str(inferred_result_type.sign_value)
103
+ )
104
+ if not result_type.has_fraction_digits:
105
+ result_type.fraction_digits = Expression(
106
+ expr=str(inferred_result_type.fraction_digits_value)
107
+ )
108
+
109
+ @staticmethod
110
+ def _same_numeric_attributes(
111
+ result_type: QuantumNumeric, inferred_result_type: QuantumNumeric
112
+ ) -> bool:
113
+ return (
114
+ result_type.size_in_bits == inferred_result_type.size_in_bits
115
+ and result_type.sign_value == inferred_result_type.sign_value
116
+ and result_type.fraction_digits_value
117
+ == inferred_result_type.fraction_digits_value
118
+ )
119
+
120
+ @classmethod
121
+ def _validate_declared_attributes(
122
+ cls, result_type: QuantumNumeric, inferred_result_type: QuantumNumeric, var: str
123
+ ) -> None:
124
+ result_size, result_sign, result_fractions = (
125
+ result_type.size_in_bits,
126
+ result_type.sign_value,
127
+ result_type.fraction_digits_value,
128
+ )
129
+ inferred_size, inferred_sign, inferred_fractions = (
130
+ inferred_result_type.size_in_bits,
131
+ inferred_result_type.sign_value,
132
+ inferred_result_type.fraction_digits_value,
133
+ )
134
+ result_integers = result_size - result_fractions
135
+ inferred_integers = inferred_size - inferred_fractions
136
+
137
+ if (
138
+ (result_integers < inferred_integers)
139
+ or (result_fractions < inferred_fractions)
140
+ or (not result_sign and inferred_sign)
141
+ or (
142
+ result_sign
143
+ and not inferred_sign
144
+ and result_integers == inferred_integers
145
+ )
146
+ ):
147
+ if (
148
+ not result_sign
149
+ and result_fractions == 0
150
+ and not inferred_sign
151
+ and inferred_fractions == 0
152
+ ):
153
+ result_size_str = f"size {result_size}"
154
+ inferred_size_str = f"size {inferred_size}"
155
+ hint = f"Hint: increase the size in the declaration of {var!r} or omit it to enable automatic inference."
156
+ else:
157
+ result_size_str = f"size {result_size}, {'signed' if result_sign else 'unsigned'}, and {result_fractions} fraction digits"
158
+ inferred_size_str = f"size {inferred_size}, {'signed' if inferred_sign else 'unsigned'}, and {inferred_fractions} fraction digits"
159
+ hint = f"Hint: omit the numeric attributes from the declaration of {var!r} to enable automatic inference."
160
+ raise ClassiqExpansionError(
161
+ f"Cannot assign an expression with inferred {inferred_size_str} to variable {var!r} with declared {result_size_str}. {hint}"
162
+ )
163
+
164
+ @staticmethod
165
+ def _craft_size_string(size: int, is_signed: bool, fraction_digits: int) -> str:
166
+ extra = (
167
+ f", with {fraction_digits} fraction digits" if fraction_digits > 0 else ""
168
+ )
169
+ return f"{size} ({'signed' if is_signed else 'unsigned'}{extra})"
170
+
171
+ def _assign_to_inferred_var_and_bind(
172
+ self,
173
+ op: ArithmeticOperation,
174
+ result_type: QuantumNumeric,
175
+ inferred_result_type: QuantumNumeric,
176
+ ) -> None:
177
+ handles: list[HandleBinding] = []
178
+
179
+ extra_fraction_digits = (
180
+ result_type.fraction_digits_value
181
+ - inferred_result_type.fraction_digits_value
182
+ )
183
+ if extra_fraction_digits > 0:
184
+ handles.append(
185
+ self._declare_qarray("extra_fraction_digits", extra_fraction_digits)
186
+ )
187
+
188
+ inferred_result_name = self._counted_name_allocator.allocate("inferred_result")
189
+ inferred_result_handle = HandleBinding(name=inferred_result_name)
190
+ self._interpreter.emit(
191
+ VariableDeclarationStatement(
192
+ name=inferred_result_name, quantum_type=inferred_result_type
193
+ )
194
+ )
195
+ handles.append(inferred_result_handle)
196
+ modified_op = op.model_copy(update={"result_var": inferred_result_handle})
197
+ self._interpreter.add_to_debug_info(modified_op)
198
+ self._interpreter.emit(modified_op)
199
+
200
+ result_integer_size = (
201
+ result_type.size_in_bits - result_type.fraction_digits_value
202
+ )
203
+ inferred_result_integer_size = (
204
+ inferred_result_type.size_in_bits
205
+ - inferred_result_type.fraction_digits_value
206
+ )
207
+ extra_integers = result_integer_size - inferred_result_integer_size
208
+ if extra_integers > 0:
209
+ handles.append(self._declare_qarray("extra_integers", extra_integers))
210
+
211
+ self._interpreter.emit(
212
+ BindOperation(in_handles=handles, out_handles=[op.result_var])
213
+ )
214
+
215
+ if (
216
+ result_type.sign_value
217
+ and inferred_result_type.sign_value
218
+ and extra_integers > 0
219
+ ):
220
+ sign_idx = result_type.size_in_bits - extra_integers - 1
221
+ self._sign_extension(op.result_var, sign_idx, result_type.size_in_bits)
222
+
223
+ def _declare_qarray(
224
+ self,
225
+ prefix: str,
226
+ size: int,
227
+ allocate: bool = True,
228
+ ) -> HandleBinding:
229
+ name = self._counted_name_allocator.allocate(prefix)
230
+ handle = HandleBinding(name=name)
231
+ quantum_type = QuantumBitvector(length=Expression(expr=str(size)))
232
+ self._interpreter.emit(
233
+ VariableDeclarationStatement(name=name, quantum_type=quantum_type)
234
+ )
235
+ if allocate:
236
+ self._interpreter.emit(Allocate(target=handle))
237
+ return handle
238
+
239
+ def _sign_extension(
240
+ self,
241
+ result_var: ConcreteHandleBinding,
242
+ sign_idx: int,
243
+ size: int,
244
+ ) -> None:
245
+ aux = self._declare_qarray("inferred_result_aux", size, allocate=False)
246
+ self._interpreter.emit(
247
+ WithinApply(
248
+ compute=[BindOperation(in_handles=[result_var], out_handles=[aux])],
249
+ action=[
250
+ QuantumFunctionCall(
251
+ function=CX.func_decl.name,
252
+ positional_args=[
253
+ SubscriptHandleBinding(
254
+ base_handle=aux, index=Expression(expr=str(sign_idx))
255
+ ),
256
+ SubscriptHandleBinding(
257
+ base_handle=aux, index=Expression(expr=str(idx))
258
+ ),
259
+ ],
260
+ )
261
+ for idx in range(sign_idx + 1, size)
262
+ ],
263
+ )
63
264
  )
@@ -1,3 +1,5 @@
1
+ from typing import TYPE_CHECKING
2
+
1
3
  from classiq.interface.exceptions import (
2
4
  ClassiqExpansionError,
3
5
  ClassiqInternalExpansionError,
@@ -14,41 +16,21 @@ from classiq.model_expansions.evaluators.quantum_type_utils import (
14
16
  from classiq.model_expansions.quantum_operations.emitter import Emitter
15
17
  from classiq.model_expansions.scope import Evaluated, QuantumSymbol
16
18
 
19
+ if TYPE_CHECKING:
20
+ from classiq.model_expansions.interpreters.base_interpreter import BaseInterpreter
21
+
17
22
 
18
23
  class BindEmitter(Emitter[BindOperation]):
24
+ def __init__(
25
+ self, interpreter: "BaseInterpreter", allow_symbolic_size: bool = False
26
+ ) -> None:
27
+ super().__init__(interpreter)
28
+ self._allow_symbolic_size = allow_symbolic_size
29
+
19
30
  def emit(self, bind: BindOperation, /) -> bool:
20
31
  inputs, outputs = self._get_inputs_outputs(bind)
21
32
  validate_bind_targets(bind, self._current_scope)
22
- unsized_outputs = [
23
- output for output in outputs if not output.quantum_type.has_size_in_bits
24
- ]
25
-
26
- if len(unsized_outputs) > 1:
27
- raise ClassiqExpansionError(
28
- f"Cannot perform the split operation {bind.in_handles[0].name} -> {{{', '.join(out_handle.name for out_handle in bind.out_handles)}}}:\n"
29
- f"Quantum variables {', '.join(str(out_handle.handle) for out_handle in unsized_outputs)} are used as bind outputs, but their size cannot be inferred."
30
- )
31
-
32
- input_size = sum(input.quantum_type.size_in_bits for input in inputs)
33
- output_size = sum(
34
- output.quantum_type.size_in_bits
35
- for output in outputs
36
- if output.quantum_type.has_size_in_bits
37
- )
38
-
39
- if len(unsized_outputs) == 1:
40
- set_size(
41
- unsized_outputs[0].quantum_type,
42
- input_size - output_size,
43
- str(unsized_outputs[0].handle),
44
- )
45
-
46
- else:
47
- if input_size != output_size:
48
- raise ClassiqExpansionError(
49
- 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}"
50
- )
51
-
33
+ self._process_var_sizes(bind, inputs, outputs)
52
34
  self.emit_statement(
53
35
  BindOperation(
54
36
  in_handles=bind.in_handles,
@@ -111,3 +93,45 @@ class BindEmitter(Emitter[BindOperation]):
111
93
  f"{out.value.handle.name!r} on the right-hand side of a bind "
112
94
  f"statement"
113
95
  )
96
+
97
+ def _process_var_sizes(
98
+ self,
99
+ bind: BindOperation,
100
+ inputs: list[QuantumSymbol],
101
+ outputs: list[QuantumSymbol],
102
+ ) -> None:
103
+ unsized_inputs = [
104
+ input for input in inputs if not input.quantum_type.has_size_in_bits
105
+ ]
106
+ if len(unsized_inputs) > 0:
107
+ if self._allow_symbolic_size:
108
+ return
109
+ raise ClassiqInternalExpansionError("Uninitialized bind inputs")
110
+
111
+ unsized_outputs = [
112
+ output for output in outputs if not output.quantum_type.has_size_in_bits
113
+ ]
114
+ if len(unsized_outputs) > 1:
115
+ if self._allow_symbolic_size:
116
+ return
117
+ raise ClassiqExpansionError(
118
+ f"Cannot perform the split operation {bind.in_handles[0].name} -> {{{', '.join(out_handle.name for out_handle in bind.out_handles)}}}:\n"
119
+ f"Quantum variables {', '.join(str(out_handle.handle) for out_handle in unsized_outputs)} are used as bind outputs, but their size cannot be inferred."
120
+ )
121
+
122
+ input_size = sum(input.quantum_type.size_in_bits for input in inputs)
123
+ output_size = sum(
124
+ output.quantum_type.size_in_bits
125
+ for output in outputs
126
+ if output.quantum_type.has_size_in_bits
127
+ )
128
+ if len(unsized_outputs) == 1:
129
+ set_size(
130
+ unsized_outputs[0].quantum_type,
131
+ input_size - output_size,
132
+ str(unsized_outputs[0].handle),
133
+ )
134
+ elif input_size != output_size:
135
+ raise ClassiqExpansionError(
136
+ 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}"
137
+ )
@@ -1,8 +1,14 @@
1
1
  from collections.abc import Sequence
2
2
  from typing import TYPE_CHECKING
3
3
 
4
+ from classiq.interface.generator.functions.builtins.internal_operators import (
5
+ CLASSICAL_IF_OPERATOR_NAME,
6
+ REPEAT_OPERATOR_NAME,
7
+ )
8
+ from classiq.interface.model.classical_if import ClassicalIf
4
9
  from classiq.interface.model.quantum_function_declaration import PositionalArg
5
10
  from classiq.interface.model.quantum_statement import QuantumOperation, QuantumStatement
11
+ from classiq.interface.model.repeat import Repeat
6
12
 
7
13
  from classiq.model_expansions.closure import Closure
8
14
  from classiq.model_expansions.quantum_operations.emitter import Emitter
@@ -88,3 +94,39 @@ class BlockEvaluator(Emitter[QuantumOperation]):
88
94
 
89
95
  def get_scope(self, op: QuantumOperation) -> Scope:
90
96
  return Scope(parent=self._current_scope)
97
+
98
+
99
+ class IfElimination(BlockEvaluator):
100
+ def __init__(self, interpreter: "BaseInterpreter") -> None:
101
+ super().__init__(interpreter, CLASSICAL_IF_OPERATOR_NAME, "then", "else_")
102
+
103
+ def emit(self, op: ClassicalIf, /) -> bool: # type:ignore[override]
104
+ cond = op.condition.value.value
105
+ if not isinstance(cond, bool):
106
+ return False
107
+ if op.is_generative():
108
+ if cond:
109
+ then_block = op.get_generative_block("then")
110
+ op.clear_generative_blocks()
111
+ op.set_generative_block("then", then_block)
112
+ elif not op.has_generative_block("else_"):
113
+ op.clear_generative_blocks()
114
+ else:
115
+ else_block = op.get_generative_block("else_")
116
+ op.clear_generative_blocks()
117
+ op.set_generative_block("else_", else_block)
118
+ else:
119
+ if cond:
120
+ op = op.model_copy(update={"else_": []})
121
+ else:
122
+ op = op.model_copy(update={"then": []})
123
+ return super().emit(op)
124
+
125
+
126
+ class RepeatElimination(BlockEvaluator):
127
+ def __init__(self, interpreter: "BaseInterpreter") -> None:
128
+ super().__init__(interpreter, REPEAT_OPERATOR_NAME, "body")
129
+
130
+ def emit(self, op: Repeat, /) -> bool: # type:ignore[override]
131
+ count = op.count.value.value
132
+ return isinstance(count, int) and count == 0
@@ -4,13 +4,17 @@ from typing import (
4
4
  TYPE_CHECKING,
5
5
  Any,
6
6
  Generic,
7
+ Optional,
7
8
  cast,
8
9
  )
9
10
  from uuid import UUID
10
11
 
11
12
  import sympy
12
13
 
13
- from classiq.interface.debug_info.debug_info import FunctionDebugInfo
14
+ from classiq.interface.debug_info.debug_info import (
15
+ FunctionDebugInfo,
16
+ calculate_port_to_passed_variable_mapping,
17
+ )
14
18
  from classiq.interface.exceptions import ClassiqExpansionError
15
19
  from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
16
20
  AnyClassicalValue,
@@ -59,7 +63,13 @@ from classiq.model_expansions.quantum_operations.emitter import (
59
63
  Emitter,
60
64
  QuantumStatementT,
61
65
  )
62
- from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
66
+ from classiq.model_expansions.scope import (
67
+ Evaluated,
68
+ QuantumSymbol,
69
+ QuantumSymbolList,
70
+ QuantumVariable,
71
+ Scope,
72
+ )
63
73
  from classiq.model_expansions.transformers.var_splitter import VarSplitter
64
74
  from classiq.model_expansions.utils.text_utils import are, readable_list, s
65
75
  from classiq.qmod.semantics.validation.signature_validation import (
@@ -71,11 +81,14 @@ if TYPE_CHECKING:
71
81
 
72
82
 
73
83
  def _validate_cloning(evaluated_args: list[Evaluated]) -> None:
74
- handles = [
75
- arg.value.handle
84
+ handles = chain.from_iterable(
85
+ (
86
+ [arg.value.handle]
87
+ if isinstance(arg.value, QuantumSymbol)
88
+ else arg.value.handles if isinstance(arg.value, QuantumSymbolList) else []
89
+ )
76
90
  for arg in evaluated_args
77
- if isinstance(arg.value, QuantumSymbol)
78
- ]
91
+ )
79
92
  for handle, other_handle in combinations(handles, 2):
80
93
  if handle.overlaps(other_handle):
81
94
  if handle == other_handle:
@@ -90,13 +103,15 @@ def _validate_cloning(evaluated_args: list[Evaluated]) -> None:
90
103
 
91
104
 
92
105
  def _is_symbolic(arg: Any) -> bool:
106
+ if isinstance(arg, (ClassicalProxy, AnyClassicalValue)):
107
+ return True
93
108
  if isinstance(arg, list):
94
109
  return any(_is_symbolic(item) for item in arg)
95
110
  if isinstance(arg, QmodStructInstance):
96
111
  return any(_is_symbolic(item) for item in arg.fields.values())
97
112
  if isinstance(arg, sympy.Basic):
98
113
  return len(arg.free_symbols) > 0
99
- return isinstance(arg, (ClassicalProxy, AnyClassicalValue))
114
+ return False
100
115
 
101
116
 
102
117
  class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSplitter):
@@ -125,7 +140,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
125
140
  self,
126
141
  function: FunctionClosure,
127
142
  args: list[ArgValue],
128
- propagated_debug_info: FunctionDebugInfo | None,
143
+ propagated_debug_info: Optional[FunctionDebugInfo],
129
144
  ) -> QuantumFunctionCall:
130
145
  call = self._create_quantum_function_call(
131
146
  function, args, propagated_debug_info=propagated_debug_info
@@ -135,8 +150,8 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
135
150
 
136
151
  @staticmethod
137
152
  def _get_back_ref(
138
- propagated_debug_info: FunctionDebugInfo | None,
139
- ) -> UUID | None:
153
+ propagated_debug_info: Optional[FunctionDebugInfo],
154
+ ) -> Optional[UUID]:
140
155
  if propagated_debug_info is None:
141
156
  return None
142
157
  if propagated_debug_info.node is None:
@@ -147,7 +162,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
147
162
  self,
148
163
  function: FunctionClosure,
149
164
  args: list[ArgValue],
150
- propagated_debug_info: FunctionDebugInfo | None,
165
+ propagated_debug_info: Optional[FunctionDebugInfo],
151
166
  ) -> QuantumFunctionCall:
152
167
  function = function.clone()
153
168
  function = function.set_depth(self._builder.current_function.depth + 1)
@@ -187,11 +202,13 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
187
202
  back_ref=self._get_back_ref(propagated_debug_info),
188
203
  )
189
204
 
190
- port_to_passed_variable_map = {
191
- arg_decl.name: str(evaluated_arg.value.handle)
192
- for arg_decl, evaluated_arg in zip(new_positional_arg_decls, evaluated_args)
193
- if isinstance(arg_decl, PortDeclaration)
194
- }
205
+ port_to_passed_variable_map = calculate_port_to_passed_variable_mapping(
206
+ new_positional_arg_decls,
207
+ [
208
+ arg.value.handle if isinstance(arg.value, QuantumSymbol) else None
209
+ for arg in evaluated_args
210
+ ],
211
+ )
195
212
  self._debug_info[new_call.uuid] = FunctionDebugInfo(
196
213
  name=new_call.func_name,
197
214
  port_to_passed_variable_map=port_to_passed_variable_map,
@@ -274,7 +291,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
274
291
  ) -> None:
275
292
  for parameter, argument in zip(parameters, arguments):
276
293
  param_handle = HandleBinding(name=parameter.name)
277
- if isinstance(argument.value, QuantumSymbol):
294
+ if isinstance(argument.value, QuantumVariable):
278
295
  assert isinstance(parameter, PortDeclaration)
279
296
  closure.scope[parameter.name] = Evaluated(
280
297
  QuantumSymbol(
@@ -307,7 +324,7 @@ class CallEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT], VarSpl
307
324
  positional_args = [
308
325
  arg.emit()
309
326
  for arg in evaluated_args
310
- if isinstance(arg.value, QuantumSymbol) or _is_symbolic(arg.value)
327
+ if isinstance(arg.value, QuantumVariable) or _is_symbolic(arg.value)
311
328
  ]
312
329
 
313
330
  return positional_args
@@ -23,5 +23,5 @@ class CompositeEmitter(Generic[QuantumStatementT], Emitter[QuantumStatementT]):
23
23
  for emitter in self._emitters:
24
24
  if emitter.emit(statement):
25
25
  return True
26
- self._builder.emit_statement(statement)
26
+ self.emit_statement(statement)
27
27
  return True