classiq 0.46.1__py3-none-any.whl → 0.48.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 (71) hide show
  1. classiq/_internals/api_wrapper.py +45 -8
  2. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +2 -7
  3. classiq/applications/grover/grover_model_constructor.py +2 -1
  4. classiq/execution/execution_session.py +133 -45
  5. classiq/execution/jobs.py +120 -1
  6. classiq/interface/_version.py +1 -1
  7. classiq/interface/backend/quantum_backend_providers.py +0 -1
  8. classiq/interface/debug_info/debug_info.py +23 -1
  9. classiq/interface/execution/primitives.py +17 -0
  10. classiq/interface/executor/iqae_result.py +3 -3
  11. classiq/interface/executor/result.py +3 -1
  12. classiq/interface/generator/arith/arithmetic_operations.py +5 -2
  13. classiq/interface/generator/arith/binary_ops.py +21 -14
  14. classiq/interface/generator/arith/extremum_operations.py +9 -1
  15. classiq/interface/generator/arith/number_utils.py +6 -0
  16. classiq/interface/generator/arith/register_user_input.py +30 -21
  17. classiq/interface/generator/arith/unary_ops.py +13 -1
  18. classiq/interface/generator/expressions/expression.py +8 -0
  19. classiq/interface/generator/functions/type_name.py +1 -3
  20. classiq/interface/generator/generated_circuit_data.py +47 -2
  21. classiq/interface/generator/quantum_program.py +10 -2
  22. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +17 -3
  23. classiq/interface/ide/visual_model.py +10 -5
  24. classiq/interface/interface_version.py +1 -1
  25. classiq/interface/model/bind_operation.py +0 -3
  26. classiq/interface/model/phase_operation.py +11 -0
  27. classiq/interface/model/port_declaration.py +1 -12
  28. classiq/interface/model/quantum_expressions/arithmetic_operation.py +34 -6
  29. classiq/interface/model/quantum_lambda_function.py +4 -1
  30. classiq/interface/model/quantum_statement.py +16 -1
  31. classiq/interface/model/quantum_variable_declaration.py +0 -22
  32. classiq/interface/model/statement_block.py +3 -0
  33. classiq/interface/server/global_versions.py +4 -4
  34. classiq/interface/server/routes.py +0 -3
  35. classiq/model_expansions/capturing/propagated_var_stack.py +5 -2
  36. classiq/model_expansions/closure.py +7 -2
  37. classiq/model_expansions/evaluators/quantum_type_utils.py +0 -7
  38. classiq/model_expansions/generative_functions.py +146 -28
  39. classiq/model_expansions/interpreter.py +17 -5
  40. classiq/model_expansions/quantum_operations/classicalif.py +27 -10
  41. classiq/model_expansions/quantum_operations/control.py +22 -15
  42. classiq/model_expansions/quantum_operations/emitter.py +68 -7
  43. classiq/model_expansions/quantum_operations/expression_operation.py +25 -16
  44. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +167 -95
  45. classiq/model_expansions/quantum_operations/invert.py +12 -6
  46. classiq/model_expansions/quantum_operations/phase.py +189 -0
  47. classiq/model_expansions/quantum_operations/power.py +9 -8
  48. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +20 -5
  49. classiq/model_expansions/quantum_operations/quantum_function_call.py +1 -1
  50. classiq/model_expansions/quantum_operations/repeat.py +32 -13
  51. classiq/model_expansions/quantum_operations/within_apply.py +19 -6
  52. classiq/model_expansions/scope.py +16 -5
  53. classiq/model_expansions/scope_initialization.py +11 -1
  54. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +23 -1
  55. classiq/model_expansions/visitors/variable_references.py +11 -7
  56. classiq/qmod/builtins/__init__.py +10 -0
  57. classiq/qmod/builtins/constants.py +10 -0
  58. classiq/qmod/builtins/functions/state_preparation.py +4 -1
  59. classiq/qmod/builtins/operations.py +55 -161
  60. classiq/qmod/create_model_function.py +1 -1
  61. classiq/qmod/generative.py +14 -5
  62. classiq/qmod/native/pretty_printer.py +14 -4
  63. classiq/qmod/pretty_print/pretty_printer.py +14 -4
  64. classiq/qmod/qmod_constant.py +28 -18
  65. classiq/qmod/qmod_variable.py +43 -23
  66. classiq/qmod/quantum_expandable.py +14 -1
  67. classiq/qmod/semantics/static_semantics_visitor.py +10 -0
  68. classiq/qmod/semantics/validation/constants_validation.py +16 -0
  69. {classiq-0.46.1.dist-info → classiq-0.48.0.dist-info}/METADATA +9 -4
  70. {classiq-0.46.1.dist-info → classiq-0.48.0.dist-info}/RECORD +71 -66
  71. {classiq-0.46.1.dist-info → classiq-0.48.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,189 @@
1
+ from typing import TYPE_CHECKING, Dict, List, Tuple
2
+
3
+ import sympy
4
+ from sympy import sympify
5
+
6
+ from classiq.interface.exceptions import ClassiqExpansionError
7
+ from classiq.interface.generator.expressions.expression import Expression
8
+ from classiq.interface.model.bind_operation import BindOperation
9
+ from classiq.interface.model.handle_binding import HANDLE_ID_SEPARATOR, HandleBinding
10
+ from classiq.interface.model.phase_operation import PhaseOperation
11
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
12
+ from classiq.interface.model.quantum_type import (
13
+ QuantumBitvector,
14
+ QuantumNumeric,
15
+ QuantumScalar,
16
+ )
17
+ from classiq.interface.model.variable_declaration_statement import (
18
+ VariableDeclarationStatement,
19
+ )
20
+ from classiq.interface.model.within_apply_operation import WithinApply
21
+
22
+ from classiq.applications.combinatorial_helpers.transformations.ising_converter import (
23
+ _find_sub_list_items,
24
+ _get_vars,
25
+ _refine_ising_expr,
26
+ _to_ising_symbolic_objective_function,
27
+ )
28
+ from classiq.model_expansions.quantum_operations.expression_operation import (
29
+ ExpressionOperationEmitter,
30
+ )
31
+ from classiq.qmod.builtins.functions.exponentiation import suzuki_trotter
32
+ from classiq.qmod.semantics.error_manager import ErrorManager
33
+
34
+
35
+ class PhaseEmitter(ExpressionOperationEmitter[PhaseOperation]):
36
+ def _negate_expression(self, expression: Expression, /) -> Expression:
37
+ return self._evaluate_expression(
38
+ # TODO: change to model_copy for pydantic V2
39
+ expression.copy(update=dict(expr=f"-({expression.expr})"))
40
+ )
41
+
42
+ def emit(self, phase_op: PhaseOperation, /) -> None:
43
+ phase_expression = self._evaluate_op_expression(phase_op)
44
+ phase_op = phase_op.copy(update=dict(expression=phase_expression))
45
+ arrays_with_subscript = self._get_symbols_to_split(phase_op.expression)
46
+ if len(arrays_with_subscript) > 0:
47
+ self._emit_with_split(phase_op, phase_op.expression, arrays_with_subscript)
48
+ return
49
+ # TODO: change to model_copy for pydantic V2
50
+ phase_op = phase_op.copy(
51
+ update=dict(expression=self._negate_expression(phase_op.expression))
52
+ )
53
+ phase_op = self._evaluate_types_in_expression(phase_op, phase_op.expression)
54
+ if len(phase_op.var_handles) == 0:
55
+ ErrorManager().add_error(
56
+ "Cannot perform phase operation on an expression with no quantum variables."
57
+ )
58
+ return
59
+
60
+ aux_name = self._counted_name_allocator.allocate("phase_aux")
61
+ if len(phase_op.var_handles) > 1:
62
+ split_join = True
63
+ evolution_variable = HandleBinding(name=aux_name)
64
+ else:
65
+ split_join = False
66
+ evolution_variable = phase_op.var_handles[0]
67
+ expression = self._evaluate_op_expression(phase_op)
68
+ expression_evolution_function = QuantumFunctionCall(
69
+ function=suzuki_trotter.func_decl.name,
70
+ positional_args=[
71
+ _convert_cost_expression_to_hamiltonian(
72
+ expression.expr,
73
+ {
74
+ var.name: self._current_scope[var.name].value.quantum_type
75
+ for var in phase_op.var_handles
76
+ },
77
+ ),
78
+ phase_op.theta,
79
+ Expression(expr="1"),
80
+ Expression(expr="1"),
81
+ evolution_variable,
82
+ ],
83
+ source_ref=phase_op.source_ref,
84
+ )
85
+ expression_evolution_function.set_func_decl(suzuki_trotter.func_decl)
86
+
87
+ if split_join:
88
+ self._interpreter.emit_statement(
89
+ VariableDeclarationStatement(
90
+ name=aux_name, quantum_type=QuantumBitvector()
91
+ )
92
+ )
93
+ self._interpreter.emit_statement(
94
+ WithinApply(
95
+ compute=[
96
+ BindOperation(
97
+ in_handles=phase_op.var_handles,
98
+ out_handles=[HandleBinding(name=aux_name)],
99
+ )
100
+ ],
101
+ action=[expression_evolution_function],
102
+ source_ref=phase_op.source_ref,
103
+ )
104
+ )
105
+ else:
106
+ self._interpreter.emit_statement(
107
+ expression_evolution_function,
108
+ )
109
+
110
+
111
+ def _get_single_bit_vars_expression(
112
+ expr: sympy.Expr, vars_info: Dict[str, QuantumScalar]
113
+ ) -> Tuple[sympy.Expr, List[sympy.Symbol]]:
114
+ bit_vars = []
115
+ for var_name, var_info in vars_info.items():
116
+ size = var_info.size_in_bits
117
+ var = sympy.Symbol(var_name)
118
+ if size == 1:
119
+ bits = [var]
120
+ is_signed = False
121
+ fraction_places = 0
122
+ else:
123
+ if TYPE_CHECKING:
124
+ assert isinstance(var_info, QuantumNumeric)
125
+ bits = [
126
+ sympy.Symbol(f"{var_name}{HANDLE_ID_SEPARATOR}{i}__split__")
127
+ for i in range(size)
128
+ ]
129
+ is_signed = var_info.sign_value
130
+ fraction_places = var_info.fraction_digits_value
131
+ bit_vars.extend(bits)
132
+ split_var = 0
133
+ for i, bit in enumerate(bits):
134
+ if is_signed and i == size - 1: # sign bit (MSB)
135
+ split_var -= bit * 2 ** (size - 1 - fraction_places)
136
+ else:
137
+ split_var += bit * 2 ** (i - fraction_places)
138
+ expr = expr.subs(var, split_var)
139
+ return expr, bit_vars
140
+
141
+
142
+ def _convert_ising_sympy_to_pauli_terms(
143
+ ising_expr: sympy.Expr, ordered_sympy_vars: List[sympy.Symbol]
144
+ ) -> str:
145
+ pauli_terms: List[str] = []
146
+ coefficients = ising_expr.as_coefficients_dict(*ordered_sympy_vars)
147
+ for expr_term in ising_expr.args:
148
+ expr_vars = _get_vars(expr_term)
149
+ z_vec = _find_sub_list_items(ordered_sympy_vars, expr_vars)
150
+ pauli_elements = ["I"] * len(z_vec)
151
+ for index, is_z_op in enumerate(z_vec):
152
+ if is_z_op:
153
+ pauli_elements[len(z_vec) - index - 1] = (
154
+ "Z" # reminder: Pauli reverses the order!
155
+ )
156
+ term_var = sympy.Mul(
157
+ *(var for i, var in enumerate(ordered_sympy_vars) if z_vec[i])
158
+ )
159
+ coeff = float(coefficients[term_var])
160
+ paulis = [f"Pauli.{pauli}" for pauli in pauli_elements]
161
+ pauli_terms.append(
162
+ # fmt: off
163
+ "struct_literal("
164
+ "PauliTerm,"
165
+ f"pauli=[{', '.join(paulis)}],"
166
+ f"coefficient={Expression(expr=str(coeff))},"
167
+ ")"
168
+ # fmt: on,
169
+ )
170
+ return f"[{', '.join(pauli_terms)}]"
171
+
172
+
173
+ def _convert_cost_expression_to_hamiltonian(
174
+ expr: str,
175
+ vars: Dict[str, QuantumScalar],
176
+ ) -> Expression:
177
+ sympy_expr = sympify(expr)
178
+ single_bit_vars_expression, single_bit_vars = _get_single_bit_vars_expression(
179
+ sympy_expr, vars
180
+ )
181
+ if not single_bit_vars_expression.is_polynomial():
182
+ raise ClassiqExpansionError(f"phased expression {expr!r} is not polynomial")
183
+
184
+ ising_expr = _to_ising_symbolic_objective_function(single_bit_vars_expression)
185
+ ising_expr = _refine_ising_expr(ising_expr)
186
+
187
+ return Expression(
188
+ expr=_convert_ising_sympy_to_pauli_terms(ising_expr, single_bit_vars)
189
+ )
@@ -2,7 +2,6 @@ from typing import Union
2
2
 
3
3
  import sympy
4
4
 
5
- from classiq.interface.exceptions import ClassiqExpansionError
6
5
  from classiq.interface.generator.expressions.evaluated_expression import (
7
6
  EvaluatedExpression,
8
7
  )
@@ -22,6 +21,14 @@ class PowerEmitter(Emitter[Power]):
22
21
  _power_expr: Expression
23
22
 
24
23
  def emit(self, power: Power, /) -> None:
24
+ with self._propagated_var_stack.capture_variables(power):
25
+ self._emit_propagated(power)
26
+
27
+ def _emit_propagated(self, power: Power) -> None:
28
+ if power.is_generative():
29
+ context = self._register_generative_context(power, POWER_OPERATOR_NAME)
30
+ power = power.copy(update={"body": context.statements("body")})
31
+
25
32
  self._power = power
26
33
  self._power_value = self._get_power_value()
27
34
  self._power_expr = Expression(
@@ -41,8 +48,7 @@ class PowerEmitter(Emitter[Power]):
41
48
  blocks=dict(body=self._power.body),
42
49
  scope=Scope(parent=self._current_scope),
43
50
  )
44
- with self._propagated_var_stack.capture_variables(self._power):
45
- context = self._expand_operation(power_operation)
51
+ context = self._expand_operation(power_operation)
46
52
  self._builder.emit_statement(
47
53
  Power(
48
54
  body=context.statements("body"),
@@ -66,9 +72,4 @@ class PowerEmitter(Emitter[Power]):
66
72
 
67
73
  def _get_power_value(self) -> Union[int, sympy.Basic]:
68
74
  power_value = self._interpreter.evaluate(self._power.power).value
69
- if not (isinstance(power_value, int) or power_value.is_symbol):
70
- raise ClassiqExpansionError(
71
- f"`power`'s argument should be an integer or identifier. Complex "
72
- f"expressions are not supported. Got {str(power_value)!r}"
73
- )
74
75
  return power_value
@@ -11,6 +11,7 @@ from classiq.interface.model.inplace_binary_operation import (
11
11
  )
12
12
  from classiq.interface.model.quantum_expressions.arithmetic_operation import (
13
13
  ArithmeticOperation,
14
+ ArithmeticOperationKind,
14
15
  )
15
16
  from classiq.interface.model.quantum_expressions.quantum_expression import (
16
17
  QuantumAssignmentOperation,
@@ -62,7 +63,7 @@ class QuantumAssignmentOperationEmitter(
62
63
  self, op: ArithmeticOperation, expression: Expression
63
64
  ) -> None:
64
65
  op, expression, is_bool_opt = self._optimize_boolean_expression(op, expression)
65
- if op.inplace_result:
66
+ if op.is_inplace:
66
67
  self._emit_inplace_arithmetic_op(op, expression, is_bool_opt)
67
68
  else:
68
69
  self._emit_general_assignment_operation(op)
@@ -70,7 +71,9 @@ class QuantumAssignmentOperationEmitter(
70
71
  def _emit_inplace_arithmetic_op(
71
72
  self, op: ArithmeticOperation, expression: Expression, is_bool_opt: bool
72
73
  ) -> None:
73
- if op.result_type.size_in_bits > 1 or not _is_res_boolean(op):
74
+ if op.get_operation_kind() != ArithmeticOperationKind.InplaceXor or (
75
+ op.result_type.size_in_bits > 1 or not _is_res_boolean(op)
76
+ ):
74
77
  _validate_naive_inplace_handles(op)
75
78
  self._build_naive_inplace(op, expression)
76
79
  return
@@ -100,7 +103,15 @@ class QuantumAssignmentOperationEmitter(
100
103
  def _optimize_boolean_expression(
101
104
  self, op: ArithmeticOperation, expression: Expression
102
105
  ) -> Tuple[ArithmeticOperation, Expression, bool]:
103
- if not _all_vars_boolean(op):
106
+ if (
107
+ self._interpreter._is_frontend
108
+ or op.get_operation_kind()
109
+ not in (
110
+ ArithmeticOperationKind.Assignment,
111
+ ArithmeticOperationKind.InplaceXor,
112
+ )
113
+ or not _all_vars_boolean(op)
114
+ ):
104
115
  return op, expression, False
105
116
  optimizer = BooleanExpressionOptimizer()
106
117
  optimized_expression = Expression(
@@ -130,10 +141,14 @@ class QuantumAssignmentOperationEmitter(
130
141
  arith_expression = ArithmeticOperation(
131
142
  result_var=HandleBinding(name=aux_var),
132
143
  expression=new_expression,
133
- inplace_result=False,
144
+ operation_kind=ArithmeticOperationKind.Assignment,
134
145
  )
146
+ if qe.get_operation_kind() == ArithmeticOperationKind.InplaceXor:
147
+ op = BinaryOperation.Xor
148
+ else:
149
+ op = BinaryOperation.Addition
135
150
  inplace_store = InplaceBinaryOperation(
136
- operation=BinaryOperation.Xor,
151
+ operation=op,
137
152
  target=qe.result_var,
138
153
  value=HandleBinding(name=aux_var),
139
154
  )
@@ -11,5 +11,5 @@ class QuantumFunctionCallEmitter(Emitter[QuantumFunctionCall]):
11
11
  args = call.positional_args
12
12
  with ErrorManager().call(
13
13
  function.name
14
- ), self._propagated_var_stack.capture_variables(call):
14
+ ), function.scope.freeze(), self._propagated_var_stack.capture_variables(call):
15
15
  self._emit_quantum_function_call(function, args)
@@ -1,3 +1,5 @@
1
+ from typing import Type
2
+
1
3
  from classiq.interface.generator.expressions.expression import Expression
2
4
  from classiq.interface.generator.functions.builtins.internal_operators import (
3
5
  REPEAT_OPERATOR_NAME,
@@ -8,9 +10,10 @@ from classiq.interface.model.classical_parameter_declaration import (
8
10
  )
9
11
  from classiq.interface.model.repeat import Repeat
10
12
 
11
- from classiq.model_expansions.closure import FunctionClosure
13
+ from classiq.model_expansions.closure import FunctionClosure, GenerativeFunctionClosure
12
14
  from classiq.model_expansions.quantum_operations.emitter import Emitter
13
15
  from classiq.model_expansions.scope import Scope
16
+ from classiq.qmod.quantum_function import GenerativeQFunc
14
17
 
15
18
 
16
19
  class RepeatEmitter(Emitter[Repeat]):
@@ -18,16 +21,32 @@ class RepeatEmitter(Emitter[Repeat]):
18
21
  count = self._interpreter.evaluate(repeat.count).as_type(int)
19
22
  for i in range(count):
20
23
  with self._propagated_var_stack.capture_variables(repeat):
21
- iteration_function = FunctionClosure.create(
22
- name=REPEAT_OPERATOR_NAME,
23
- positional_arg_declarations=[
24
- ClassicalParameterDeclaration(
25
- name=repeat.iter_var, classical_type=Integer()
26
- )
27
- ],
28
- body=repeat.body,
29
- scope=Scope(parent=self._current_scope),
30
- )
31
- self._emit_quantum_function_call(
32
- iteration_function, [Expression(expr=str(i))]
24
+ self._emit_propagated(repeat, i)
25
+
26
+ def _emit_propagated(self, repeat: Repeat, i: int) -> None:
27
+ closure_constructor: Type[FunctionClosure]
28
+ extra_args: dict
29
+ if repeat.is_generative():
30
+ closure_constructor = GenerativeFunctionClosure
31
+ extra_args = {
32
+ "generative_blocks": {
33
+ "body": GenerativeQFunc(
34
+ repeat.get_generative_block("body"),
35
+ ),
36
+ }
37
+ }
38
+ else:
39
+ closure_constructor = FunctionClosure
40
+ extra_args = {}
41
+ iteration_function = closure_constructor.create(
42
+ name=REPEAT_OPERATOR_NAME,
43
+ positional_arg_declarations=[
44
+ ClassicalParameterDeclaration(
45
+ name=repeat.iter_var, classical_type=Integer()
33
46
  )
47
+ ],
48
+ body=repeat.body,
49
+ scope=Scope(parent=self._current_scope),
50
+ **extra_args,
51
+ )
52
+ self._emit_quantum_function_call(iteration_function, [Expression(expr=str(i))])
@@ -11,6 +11,21 @@ from classiq.model_expansions.scope import Scope
11
11
 
12
12
  class WithinApplyEmitter(Emitter[WithinApply]):
13
13
  def emit(self, within_apply: WithinApply, /) -> None:
14
+ with self._propagated_var_stack.capture_variables(within_apply):
15
+ self._emit_propagated(within_apply)
16
+
17
+ def _emit_propagated(self, within_apply: WithinApply) -> None:
18
+ if within_apply.is_generative():
19
+ within_apply_context = self._register_generative_context(
20
+ within_apply, WITHIN_APPLY_NAME, ["within", "apply"]
21
+ )
22
+ within_apply = within_apply.copy(
23
+ update={
24
+ "compute": within_apply_context.statements("within"),
25
+ "action": within_apply_context.statements("apply"),
26
+ }
27
+ )
28
+
14
29
  if self._should_wrap(within_apply.compute):
15
30
  self._emit_wrapped(within_apply)
16
31
  return
@@ -23,8 +38,7 @@ class WithinApplyEmitter(Emitter[WithinApply]):
23
38
  blocks=dict(within=within_apply.compute, apply=within_apply.action),
24
39
  scope=Scope(parent=self._current_scope),
25
40
  )
26
- with self._propagated_var_stack.capture_variables(within_apply):
27
- context = self._expand_operation(within_apply_operation)
41
+ context = self._expand_operation(within_apply_operation)
28
42
  self._builder.emit_statement(
29
43
  WithinApply(
30
44
  compute=context.statements("within"),
@@ -34,10 +48,9 @@ class WithinApplyEmitter(Emitter[WithinApply]):
34
48
  )
35
49
 
36
50
  def _emit_wrapped(self, within_apply: WithinApply) -> None:
37
- with self._propagated_var_stack.capture_variables(within_apply):
38
- wrapped_compute = self._create_expanded_wrapping_function(
39
- COMPUTE_OPERATOR_NAME, within_apply.compute
40
- )
51
+ wrapped_compute = self._create_expanded_wrapping_function(
52
+ COMPUTE_OPERATOR_NAME, within_apply.compute
53
+ )
41
54
  wrapped_within_apply = WithinApply(
42
55
  compute=[wrapped_compute],
43
56
  action=within_apply.action,
@@ -1,11 +1,10 @@
1
1
  import itertools
2
2
  from collections import UserDict
3
+ from contextlib import contextmanager
3
4
  from dataclasses import dataclass
4
5
  from functools import singledispatch
5
6
  from typing import TYPE_CHECKING, Any, Dict, Iterator, Optional, Type, TypeVar, Union
6
7
 
7
- from typing_extensions import Self
8
-
9
8
  from classiq.interface.exceptions import (
10
9
  ClassiqExpansionError,
11
10
  ClassiqInternalExpansionError,
@@ -180,13 +179,13 @@ class Scope(EvaluatedUserDict):
180
179
  data: Optional[Dict[str, Evaluated]] = None,
181
180
  /,
182
181
  *,
183
- parent: Optional[Self] = None,
182
+ parent: Optional["Scope"] = None,
184
183
  ) -> None:
185
184
  super().__init__(data or {})
186
- self._parent: Optional[Self] = parent
185
+ self._parent: Optional["Scope"] = parent
187
186
 
188
187
  @property
189
- def parent(self) -> Optional[Self]:
188
+ def parent(self) -> Optional["Scope"]:
190
189
  return self._parent
191
190
 
192
191
  def __getitem__(self, name: str) -> Evaluated:
@@ -224,3 +223,15 @@ class Scope(EvaluatedUserDict):
224
223
  (self.data or {}) | (other.data or {}),
225
224
  parent=parent,
226
225
  )
226
+
227
+ def _copy(self) -> "Scope":
228
+ return Scope(
229
+ self.data, parent=None if self._parent is None else self._parent.copy()
230
+ )
231
+
232
+ @contextmanager
233
+ def freeze(self) -> Iterator[None]:
234
+ previous = self._copy()
235
+ yield
236
+ self.data = previous.data
237
+ self._parent = previous._parent
@@ -1,5 +1,6 @@
1
1
  from typing import List, Sequence
2
2
 
3
+ from classiq.interface.exceptions import ClassiqError
3
4
  from classiq.interface.generator.constant import Constant
4
5
  from classiq.interface.generator.expressions.expression_constants import (
5
6
  CPARAM_EXECUTION_SUFFIX,
@@ -22,6 +23,7 @@ from classiq.model_expansions.evaluators.parameter_types import (
22
23
  )
23
24
  from classiq.model_expansions.expression_renamer import ExpressionRenamer
24
25
  from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
26
+ from classiq.qmod.builtins import BUILTIN_CONSTANTS
25
27
  from classiq.qmod.builtins.functions import (
26
28
  CORE_LIB_DECLS,
27
29
  OPEN_LIB_DECLS,
@@ -71,7 +73,7 @@ def _add_generative_functions_to_scope(
71
73
  name=function.func_decl.name,
72
74
  positional_arg_declarations=function.func_decl.positional_arg_declarations,
73
75
  scope=Scope(parent=scope),
74
- generative_function=function,
76
+ generative_blocks={"body": function},
75
77
  )
76
78
  )
77
79
 
@@ -104,6 +106,14 @@ def _init_builtins_scope(scope: Scope) -> None:
104
106
  is_atomic=True,
105
107
  )
106
108
  )
109
+ for constant in BUILTIN_CONSTANTS:
110
+ value = constant.value
111
+ if not value.is_evaluated():
112
+ raise ClassiqError(
113
+ f"Unevaluated built-in constants not supported. Offending constant: "
114
+ f"{constant.name} = {value}"
115
+ )
116
+ scope[constant.name] = Evaluated(value=value.value.value)
107
117
 
108
118
 
109
119
  def add_entry_point_params_to_scope(
@@ -1,5 +1,5 @@
1
1
  import ast
2
- from typing import TYPE_CHECKING, Dict, Type
2
+ from typing import TYPE_CHECKING, Dict, Type, cast
3
3
 
4
4
  from classiq.interface.exceptions import ClassiqExpansionError
5
5
 
@@ -116,6 +116,9 @@ class ExpressionSympyTranslator(ast.NodeTransformer):
116
116
  return self.generic_visit(node)
117
117
 
118
118
  def visit_Call(self, node: ast.Call) -> ast.AST:
119
+ if isinstance(node.func, ast.Name) and node.func.id == "Piecewise":
120
+ return self._visit_piecewise(node)
121
+
119
122
  if (
120
123
  not isinstance(node.func, ast.Name)
121
124
  or node.func.id not in self.SPECIAL_FUNCTIONS
@@ -128,6 +131,25 @@ class ExpressionSympyTranslator(ast.NodeTransformer):
128
131
  keywords=[self.visit(arg) for arg in node.keywords],
129
132
  )
130
133
 
134
+ def _visit_piecewise(self, node: ast.Call) -> ast.AST:
135
+ # sympy Piecewise expression may include bitwise operations:
136
+ # Piecewise((0, Eq(x, 0)), (0.5, Eq(x, 1) | Eq(x, 2)), (1, True))
137
+ # ^
138
+ # We should avoid converting these to 'BitwiseOr' and such.
139
+ return ast.Call(
140
+ func=node.func,
141
+ args=[
142
+ ast.Tuple(
143
+ elts=(
144
+ self.generic_visit(cast(ast.Tuple, arg).elts[0]),
145
+ cast(ast.Tuple, arg).elts[1],
146
+ )
147
+ )
148
+ for arg in node.args
149
+ ],
150
+ keywords=node.keywords,
151
+ )
152
+
131
153
  def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
132
154
  if isinstance(node.slice, ast.Slice):
133
155
  if node.slice.lower is not None:
@@ -1,6 +1,6 @@
1
1
  import ast
2
2
  from contextlib import contextmanager
3
- from typing import Dict, Iterator, Optional, Set, Union
3
+ from typing import Dict, Iterator, List, Optional, Union
4
4
 
5
5
  from classiq.interface.exceptions import (
6
6
  ClassiqExpansionError,
@@ -23,10 +23,14 @@ from classiq.interface.model.handle_binding import (
23
23
 
24
24
  class VarRefCollector(ast.NodeVisitor):
25
25
  def __init__(self, ignore_duplicated_handles: bool = False) -> None:
26
- self.var_handles: Set[HandleBinding] = set()
26
+ self._var_handles: Dict[HandleBinding, bool] = {}
27
27
  self._ignore_duplicated_handles = ignore_duplicated_handles
28
28
  self._is_nested = False
29
29
 
30
+ @property
31
+ def var_handles(self) -> List[HandleBinding]:
32
+ return list(self._var_handles)
33
+
30
34
  def visit(self, node: ast.AST) -> Union[
31
35
  SubscriptHandleBinding,
32
36
  SlicedHandleBinding,
@@ -35,8 +39,8 @@ class VarRefCollector(ast.NodeVisitor):
35
39
  None,
36
40
  ]:
37
41
  res = super().visit(node)
38
- if not self._ignore_duplicated_handles and len(self.var_handles) != len(
39
- {handle.name for handle in self.var_handles}
42
+ if not self._ignore_duplicated_handles and len(self._var_handles) != len(
43
+ {handle.name for handle in self._var_handles}
40
44
  ):
41
45
  raise ClassiqExpansionError(
42
46
  "Multiple non-identical variable references in an expression are not supported."
@@ -71,7 +75,7 @@ class VarRefCollector(ast.NodeVisitor):
71
75
  raise ClassiqInternalExpansionError("Unevaluated slice.")
72
76
 
73
77
  if not self._is_nested:
74
- self.var_handles.add(handle)
78
+ self._var_handles[handle] = True
75
79
  return handle
76
80
 
77
81
  def visit_Attribute(self, node: ast.Attribute) -> Optional[FieldHandleBinding]:
@@ -84,7 +88,7 @@ class VarRefCollector(ast.NodeVisitor):
84
88
  field=node.attr,
85
89
  )
86
90
  if not self._is_nested:
87
- self.var_handles.add(handle)
91
+ self._var_handles[handle] = True
88
92
  return handle
89
93
 
90
94
  def visit_Name(self, node: ast.Name) -> Optional[HandleBinding]:
@@ -94,7 +98,7 @@ class VarRefCollector(ast.NodeVisitor):
94
98
  return None
95
99
  handle = HandleBinding(name=node.id)
96
100
  if not self._is_nested:
97
- self.var_handles.add(handle)
101
+ self._var_handles[handle] = True
98
102
  return handle
99
103
 
100
104
  @contextmanager
@@ -6,6 +6,8 @@ from .classical_execution_primitives import (
6
6
  )
7
7
  from .classical_functions import * # noqa: F403
8
8
  from .classical_functions import __all__ as _builtin_classical_functions
9
+ from .constants import * # noqa: F403
10
+ from .constants import __all__ as _builtin_constants
9
11
  from .enums import * # noqa: F403
10
12
  from .enums import __all__ as _builtin_enums
11
13
  from .functions import * # noqa: F403
@@ -18,6 +20,13 @@ from .structs import __all__ as _builtin_structs
18
20
  FinanceFunctionInput.update_forward_refs(
19
21
  FinanceFunctionType=FinanceFunctionType # noqa: F405
20
22
  )
23
+ BUILTIN_CONSTANTS = [
24
+ constant._get_constant_node()
25
+ for constant in [
26
+ SIGNED, # noqa: F405
27
+ UNSIGNED, # noqa: F405
28
+ ]
29
+ ]
21
30
 
22
31
  __all__ = (
23
32
  _builtin_enums
@@ -26,4 +35,5 @@ __all__ = (
26
35
  + _builtin_operations
27
36
  + _builtin_classical_execution_primitives
28
37
  + _builtin_classical_functions
38
+ + _builtin_constants
29
39
  )
@@ -0,0 +1,10 @@
1
+ from classiq.qmod.cparam import CBool
2
+ from classiq.qmod.qmod_constant import QConstant
3
+
4
+ SIGNED = QConstant("SIGNED", CBool, True)
5
+ UNSIGNED = QConstant("UNSIGNED", CBool, False)
6
+
7
+ __all__ = [
8
+ "SIGNED",
9
+ "UNSIGNED",
10
+ ]
@@ -340,7 +340,10 @@ def inplace_prepare_int(value: CInt, target: QArray[QBit]) -> None:
340
340
 
341
341
 
342
342
  @qfunc(external=True)
343
- def prepare_int(value: CInt, out: Output[QNum]) -> None:
343
+ def prepare_int(
344
+ value: CInt,
345
+ out: Output[QNum[Literal["floor(log(value, 2)) + 1"], Literal[False], Literal[0]]],
346
+ ) -> None:
344
347
  """
345
348
  [Qmod Classiq-library function]
346
349