classiq 0.63.0__py3-none-any.whl → 0.64.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 (42) hide show
  1. classiq/analyzer/url_utils.py +2 -3
  2. classiq/interface/_version.py +1 -1
  3. classiq/interface/analyzer/result.py +1 -2
  4. classiq/interface/executor/result.py +6 -3
  5. classiq/interface/helpers/datastructures.py +26 -0
  6. classiq/interface/interface_version.py +1 -1
  7. classiq/interface/model/handle_binding.py +11 -3
  8. classiq/interface/server/routes.py +4 -0
  9. classiq/model_expansions/atomic_expression_functions_defs.py +6 -6
  10. classiq/model_expansions/capturing/captured_vars.py +27 -10
  11. classiq/model_expansions/evaluators/arg_type_match.py +4 -7
  12. classiq/model_expansions/evaluators/quantum_type_utils.py +15 -8
  13. classiq/model_expansions/expression_evaluator.py +8 -2
  14. classiq/model_expansions/generative_functions.py +3 -3
  15. classiq/model_expansions/interpreters/__init__.py +0 -0
  16. classiq/model_expansions/{interpreter.py → interpreters/base_interpreter.py} +26 -137
  17. classiq/model_expansions/interpreters/generative_interpreter.py +108 -0
  18. classiq/model_expansions/model_tables.py +1 -92
  19. classiq/model_expansions/quantum_operations/__init__.py +0 -10
  20. classiq/model_expansions/quantum_operations/call_emitter.py +3 -3
  21. classiq/model_expansions/quantum_operations/emitter.py +2 -2
  22. classiq/model_expansions/quantum_operations/quantum_function_call.py +2 -2
  23. classiq/model_expansions/quantum_operations/shallow_emitter.py +2 -2
  24. classiq/model_expansions/scope_initialization.py +8 -0
  25. classiq/qmod/declaration_inferrer.py +0 -3
  26. classiq/qmod/generative.py +4 -4
  27. classiq/qmod/qfunc.py +4 -2
  28. classiq/qmod/qmod_variable.py +17 -10
  29. classiq/qmod/quantum_function.py +6 -4
  30. classiq/qmod/utilities.py +7 -1
  31. {classiq-0.63.0.dist-info → classiq-0.64.0.dist-info}/METADATA +1 -1
  32. {classiq-0.63.0.dist-info → classiq-0.64.0.dist-info}/RECORD +33 -39
  33. classiq/interface/helpers/dotdict.py +0 -18
  34. classiq/model_expansions/quantum_operations/control.py +0 -290
  35. classiq/model_expansions/quantum_operations/expression_operation.py +0 -103
  36. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +0 -535
  37. classiq/model_expansions/quantum_operations/invert.py +0 -36
  38. classiq/model_expansions/quantum_operations/phase.py +0 -187
  39. classiq/model_expansions/quantum_operations/power.py +0 -71
  40. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +0 -230
  41. classiq/model_expansions/quantum_operations/within_apply.py +0 -25
  42. {classiq-0.63.0.dist-info → classiq-0.64.0.dist-info}/WHEEL +0 -0
@@ -1,187 +0,0 @@
1
- from typing import TYPE_CHECKING
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
- expression.model_copy(update=dict(expr=f"-({expression.expr})"))
39
- )
40
-
41
- def emit(self, phase_op: PhaseOperation, /) -> None:
42
- arrays_with_subscript = self.split_symbols(
43
- phase_op.expression, self._counted_name_allocator.allocate
44
- )
45
- if len(arrays_with_subscript) > 0:
46
- self._emit_with_split(phase_op, phase_op.expression, arrays_with_subscript)
47
- return
48
-
49
- phase_op = phase_op.model_copy(
50
- update=dict(expression=self._negate_expression(phase_op.expression))
51
- )
52
- phase_op = self._evaluate_types_in_expression(phase_op)
53
- if len(phase_op.var_handles) == 0:
54
- ErrorManager().add_error(
55
- "Cannot perform phase operation on an expression with no quantum variables."
56
- )
57
- return
58
-
59
- aux_name = self._counted_name_allocator.allocate("phase_aux")
60
- if len(phase_op.var_handles) > 1:
61
- split_join = True
62
- evolution_variable = HandleBinding(name=aux_name)
63
- else:
64
- split_join = False
65
- evolution_variable = phase_op.var_handles[0]
66
- expression_evolution_function = QuantumFunctionCall(
67
- function=suzuki_trotter.func_decl.name,
68
- positional_args=[
69
- _convert_cost_expression_to_hamiltonian(
70
- phase_op.expression.expr,
71
- {
72
- var.name: self._current_scope[var.name].value.quantum_type
73
- for var in phase_op.var_handles
74
- },
75
- ),
76
- phase_op.theta,
77
- Expression(expr="1"),
78
- Expression(expr="1"),
79
- evolution_variable,
80
- ],
81
- source_ref=phase_op.source_ref,
82
- )
83
- expression_evolution_function.set_func_decl(suzuki_trotter.func_decl)
84
-
85
- if split_join:
86
- self._interpreter.emit_statement(
87
- VariableDeclarationStatement(
88
- name=aux_name, quantum_type=QuantumBitvector()
89
- )
90
- )
91
- self._interpreter.emit_statement(
92
- WithinApply(
93
- compute=[
94
- BindOperation(
95
- in_handles=phase_op.var_handles,
96
- out_handles=[HandleBinding(name=aux_name)],
97
- )
98
- ],
99
- action=[expression_evolution_function],
100
- source_ref=phase_op.source_ref,
101
- )
102
- )
103
- else:
104
- self._interpreter.emit_statement(
105
- expression_evolution_function,
106
- )
107
-
108
-
109
- def _get_single_bit_vars_expression(
110
- expr: sympy.Expr, vars_info: dict[str, QuantumScalar]
111
- ) -> tuple[sympy.Expr, list[sympy.Symbol]]:
112
- bit_vars = []
113
- for var_name, var_info in vars_info.items():
114
- size = var_info.size_in_bits
115
- var = sympy.Symbol(var_name)
116
- if size == 1:
117
- bits = [var]
118
- is_signed = False
119
- fraction_places = 0
120
- else:
121
- if TYPE_CHECKING:
122
- assert isinstance(var_info, QuantumNumeric)
123
- bits = [
124
- sympy.Symbol(f"{var_name}{HANDLE_ID_SEPARATOR}{i}__split__")
125
- for i in range(size)
126
- ]
127
- is_signed = var_info.sign_value
128
- fraction_places = var_info.fraction_digits_value
129
- bit_vars.extend(bits)
130
- split_var = 0
131
- for i, bit in enumerate(bits):
132
- if is_signed and i == size - 1: # sign bit (MSB)
133
- split_var -= bit * 2 ** (size - 1 - fraction_places)
134
- else:
135
- split_var += bit * 2 ** (i - fraction_places)
136
- expr = expr.subs(var, split_var)
137
- return expr, bit_vars
138
-
139
-
140
- def _convert_ising_sympy_to_pauli_terms(
141
- ising_expr: sympy.Expr, ordered_sympy_vars: list[sympy.Symbol]
142
- ) -> str:
143
- pauli_terms: list[str] = []
144
- coefficients = ising_expr.as_coefficients_dict(*ordered_sympy_vars)
145
- for expr_term in ising_expr.args:
146
- expr_vars = _get_vars(expr_term)
147
- z_vec = _find_sub_list_items(ordered_sympy_vars, expr_vars)
148
- pauli_elements = ["I"] * len(z_vec)
149
- for index, is_z_op in enumerate(z_vec):
150
- if is_z_op:
151
- pauli_elements[len(z_vec) - index - 1] = (
152
- "Z" # reminder: Pauli reverses the order!
153
- )
154
- term_var = sympy.Mul(
155
- *(var for i, var in enumerate(ordered_sympy_vars) if z_vec[i])
156
- )
157
- coeff = float(coefficients[term_var])
158
- paulis = [f"Pauli.{pauli}" for pauli in pauli_elements]
159
- pauli_terms.append(
160
- # fmt: off
161
- "struct_literal("
162
- "PauliTerm,"
163
- f"pauli=[{', '.join(paulis)}],"
164
- f"coefficient={Expression(expr=str(coeff))},"
165
- ")"
166
- # fmt: on,
167
- )
168
- return f"[{', '.join(pauli_terms)}]"
169
-
170
-
171
- def _convert_cost_expression_to_hamiltonian(
172
- expr: str,
173
- vars: dict[str, QuantumScalar],
174
- ) -> Expression:
175
- sympy_expr = sympify(expr)
176
- single_bit_vars_expression, single_bit_vars = _get_single_bit_vars_expression(
177
- sympy_expr, vars
178
- )
179
- if not single_bit_vars_expression.is_polynomial():
180
- raise ClassiqExpansionError(f"phased expression {expr!r} is not polynomial")
181
-
182
- ising_expr = _to_ising_symbolic_objective_function(single_bit_vars_expression)
183
- ising_expr = _refine_ising_expr(ising_expr)
184
-
185
- return Expression(
186
- expr=_convert_ising_sympy_to_pauli_terms(ising_expr, single_bit_vars)
187
- )
@@ -1,71 +0,0 @@
1
- from typing import Union
2
-
3
- import sympy
4
-
5
- from classiq.interface.exceptions import ClassiqExpansionError
6
- from classiq.interface.generator.expressions.evaluated_expression import (
7
- EvaluatedExpression,
8
- )
9
- from classiq.interface.generator.expressions.expression import Expression
10
- from classiq.interface.generator.functions.builtins.internal_operators import (
11
- POWER_OPERATOR_NAME,
12
- )
13
- from classiq.interface.model.power import Power
14
-
15
- from classiq.model_expansions.closure import Closure
16
- from classiq.model_expansions.quantum_operations.call_emitter import CallEmitter
17
- from classiq.model_expansions.scope import Scope
18
-
19
-
20
- class PowerEmitter(CallEmitter[Power]):
21
- _power_value: Union[int, sympy.Basic]
22
- _power_expr: Expression
23
-
24
- def emit(self, power: Power, /) -> None:
25
- self._power = power
26
- self._power_value = self._get_power_value()
27
- self._power_expr = Expression(
28
- expr=str(self._power_value), source_ref=power.power.source_ref
29
- )
30
- self._power_expr._evaluated_expr = EvaluatedExpression(value=self._power_value)
31
-
32
- if len(power.body) > 1 and isinstance(self._power_value, sympy.Basic):
33
- self._emit_wrapped()
34
- return
35
-
36
- self._emit_as_operation(power)
37
-
38
- def _emit_as_operation(self, power: Power) -> None:
39
- power_operation = Closure(
40
- name=POWER_OPERATOR_NAME,
41
- blocks=dict(body=self._power.body),
42
- scope=Scope(parent=self._current_scope),
43
- )
44
- context = self._expand_operation(power_operation)
45
- self.emit_statement(
46
- Power(
47
- body=context.statements("body"),
48
- power=self._power_expr,
49
- source_ref=power.source_ref,
50
- )
51
- )
52
-
53
- def _emit_wrapped(self) -> None:
54
- wrapping_function = self._create_expanded_wrapping_function(
55
- POWER_OPERATOR_NAME, self._power.body
56
- )
57
- self.emit_statement(
58
- Power(
59
- body=[wrapping_function],
60
- power=self._power_expr,
61
- source_ref=self._power.source_ref,
62
- )
63
- )
64
-
65
- def _get_power_value(self) -> Union[int, sympy.Basic]:
66
- power_value = self._interpreter.evaluate(self._power.power).value
67
- if isinstance(power_value, int) and power_value < 0:
68
- raise ClassiqExpansionError(
69
- f"power exponent must be non-negative, got {power_value}"
70
- )
71
- return power_value
@@ -1,230 +0,0 @@
1
- import ast
2
- import re
3
-
4
- from classiq.interface.exceptions import (
5
- ClassiqExpansionError,
6
- ClassiqInternalExpansionError,
7
- )
8
- from classiq.interface.generator.arith.arithmetic import is_zero
9
- from classiq.interface.generator.arith.arithmetic_expression_validator import (
10
- is_constant,
11
- )
12
- from classiq.interface.generator.compiler_keywords import INPLACE_ARITH_AUX_VAR_PREFIX
13
- from classiq.interface.generator.expressions.expression import Expression
14
- from classiq.interface.model.handle_binding import HandleBinding
15
- from classiq.interface.model.inplace_binary_operation import (
16
- BinaryOperation,
17
- InplaceBinaryOperation,
18
- )
19
- from classiq.interface.model.quantum_expressions.arithmetic_operation import (
20
- ArithmeticOperation,
21
- ArithmeticOperationKind,
22
- )
23
- from classiq.interface.model.quantum_expressions.quantum_expression import (
24
- QuantumAssignmentOperation,
25
- )
26
- from classiq.interface.model.quantum_function_call import QuantumFunctionCall
27
- from classiq.interface.model.quantum_type import QuantumNumeric
28
- from classiq.interface.model.variable_declaration_statement import (
29
- VariableDeclarationStatement,
30
- )
31
- from classiq.interface.model.within_apply_operation import WithinApply
32
-
33
- from classiq.model_expansions.capturing.mangling_utils import demangle_handle
34
- from classiq.model_expansions.evaluators.quantum_type_utils import copy_type_information
35
- from classiq.model_expansions.quantum_operations.expression_operation import (
36
- ExpressionOperationEmitter,
37
- )
38
- from classiq.model_expansions.scope import QuantumSymbol
39
- from classiq.model_expansions.transformers.var_splitter import SymbolParts
40
- from classiq.model_expansions.visitors.boolean_expression_transformers import (
41
- BooleanExpressionFuncLibAdapter,
42
- BooleanExpressionOptimizer,
43
- )
44
- from classiq.qmod import builtins
45
- from classiq.qmod.builtins.functions import X, allocate
46
-
47
- HANDLE_ERROR_MESSAGE = (
48
- "Quantum variable '{handle_str}' cannot appear on both sides of the assignment"
49
- )
50
-
51
-
52
- def _is_variable(expr: str) -> bool:
53
- return re.fullmatch("[a-zA-Z_][a-zA-Z0-9_]*", expr) is not None
54
-
55
-
56
- class QuantumAssignmentOperationEmitter(
57
- ExpressionOperationEmitter[QuantumAssignmentOperation]
58
- ):
59
- def emit(self, op: QuantumAssignmentOperation, /) -> None:
60
- if self._skip_assignment(op, op.expression.expr):
61
- return
62
- arrays_with_subscript = self.split_symbols(
63
- op.expression, self._counted_name_allocator.allocate
64
- )
65
- if len(arrays_with_subscript) > 0:
66
- self._emit_with_split(op, op.expression, arrays_with_subscript)
67
- return
68
-
69
- self._emit_op(op)
70
-
71
- def _emit_op(self, op: QuantumAssignmentOperation) -> None:
72
- op = self._evaluate_types_in_expression(op)
73
- if isinstance(op, ArithmeticOperation):
74
- self._emit_arithmetic_op(op)
75
- return
76
- self._emit_general_assignment_operation(op)
77
-
78
- def _emit_arithmetic_op(self, op: ArithmeticOperation) -> None:
79
- op, expression, is_bool_opt = self._optimize_boolean_expression(op)
80
- if op.is_inplace:
81
- self._emit_inplace_arithmetic_op(op, expression, is_bool_opt)
82
- else:
83
- self._emit_general_assignment_operation(op)
84
-
85
- def _emit_inplace_arithmetic_op(
86
- self, op: ArithmeticOperation, expression: Expression, is_bool_opt: bool
87
- ) -> None:
88
- target = self._interpreter.evaluate(op.result_var).as_type(QuantumSymbol)
89
- if (
90
- op.operation_kind != ArithmeticOperationKind.InplaceXor
91
- or op.result_type.size_in_bits > 1
92
- or not self._is_res_boolean(op)
93
- or target.quantum_type.size_in_bits > 1
94
- or is_constant(expression.expr)
95
- ):
96
- _validate_naive_inplace_handles(op)
97
- self._build_naive_inplace(op, expression)
98
- return
99
-
100
- op, expression, to_invert = self._adapt_boolean_inplace(
101
- op, expression, is_bool_opt
102
- )
103
- self._emit_general_assignment_operation(op)
104
- if not to_invert:
105
- return
106
-
107
- call = QuantumFunctionCall(
108
- function=builtins.functions.X.__name__, # type:ignore[attr-defined]
109
- positional_args=[op.result_var],
110
- source_ref=op.source_ref,
111
- )
112
- call.set_func_decl(X.func_decl)
113
- self._interpreter.emit_statement(call)
114
-
115
- def _emit_general_assignment_operation(
116
- self, op: QuantumAssignmentOperation
117
- ) -> None:
118
- result = self._interpreter.evaluate(op.result_var).as_type(QuantumSymbol)
119
- copy_type_information(op.result_type, result.quantum_type, str(result.handle))
120
- self.emit_statement(op)
121
-
122
- def _optimize_boolean_expression(
123
- self, op: ArithmeticOperation
124
- ) -> tuple[ArithmeticOperation, Expression, bool]:
125
- if (
126
- op.operation_kind
127
- not in (
128
- ArithmeticOperationKind.Assignment,
129
- ArithmeticOperationKind.InplaceXor,
130
- )
131
- or not self._all_vars_boolean(op)
132
- or not self._is_res_boolean(op)
133
- ):
134
- return op, op.expression, False
135
- optimizer = BooleanExpressionOptimizer()
136
- optimized_expression = Expression(
137
- expr=ast.unparse(optimizer.visit(ast.parse(op.expression.expr)))
138
- )
139
- optimized_expression = self._evaluate_expression(optimized_expression)
140
- optimized_op = op.model_copy(update=dict(expression=optimized_expression))
141
- return optimized_op, optimized_expression, optimizer.is_convertible
142
-
143
- def _adapt_boolean_inplace(
144
- self, op: ArithmeticOperation, expression: Expression, is_bool_opt: bool
145
- ) -> tuple[ArithmeticOperation, Expression, bool]:
146
- adapter = BooleanExpressionFuncLibAdapter(is_bool_opt)
147
- adapted_expression = self._evaluate_expression(
148
- Expression(expr=ast.unparse(adapter.visit(ast.parse(expression.expr))))
149
- )
150
- adapted_op = op.model_copy(update=dict(expression=adapted_expression))
151
- return adapted_op, adapted_expression, adapter.to_invert
152
-
153
- def _build_naive_inplace(
154
- self, qe: ArithmeticOperation, new_expression: Expression
155
- ) -> None:
156
- if qe.operation_kind == ArithmeticOperationKind.InplaceXor:
157
- op = BinaryOperation.Xor
158
- elif qe.operation_kind == ArithmeticOperationKind.InplaceAdd:
159
- op = BinaryOperation.Addition
160
- else:
161
- raise ClassiqInternalExpansionError(
162
- f"Unrecognized operation kind {qe.operation_kind!r}"
163
- )
164
-
165
- if is_constant(new_expression.expr):
166
- self._interpreter.emit_statement(
167
- InplaceBinaryOperation(
168
- operation=op,
169
- target=qe.result_var,
170
- value=new_expression,
171
- )
172
- )
173
- return
174
-
175
- if _is_variable(new_expression.expr):
176
- value_var = self._interpreter.evaluate(new_expression.expr).value
177
- if isinstance(value_var, QuantumSymbol):
178
- self._interpreter.emit_statement(
179
- InplaceBinaryOperation(
180
- operation=op,
181
- target=qe.result_var,
182
- value=value_var.handle,
183
- )
184
- )
185
- return
186
-
187
- aux_var = self._counted_name_allocator.allocate(INPLACE_ARITH_AUX_VAR_PREFIX)
188
- self._interpreter.emit_statement(
189
- VariableDeclarationStatement(name=aux_var, quantum_type=QuantumNumeric())
190
- )
191
- arith_expression = ArithmeticOperation(
192
- result_var=HandleBinding(name=aux_var),
193
- expression=new_expression,
194
- operation_kind=ArithmeticOperationKind.Assignment,
195
- )
196
- inplace_store = InplaceBinaryOperation(
197
- operation=op,
198
- target=qe.result_var,
199
- value=HandleBinding(name=aux_var),
200
- )
201
- self._interpreter.emit_statement(
202
- WithinApply(compute=[arith_expression], action=[inplace_store])
203
- )
204
-
205
- def _skip_assignment(self, op: QuantumAssignmentOperation, expr: str) -> bool:
206
- if not isinstance(op, ArithmeticOperation) or not is_zero(expr):
207
- return False
208
- if op.operation_kind != ArithmeticOperationKind.Assignment:
209
- return True
210
- allocate_call = QuantumFunctionCall(
211
- function=allocate.func_decl.name,
212
- positional_args=[Expression(expr="1"), op.result_var],
213
- )
214
- allocate_call.set_func_decl(allocate.func_decl)
215
- self._interpreter.emit_statement(allocate_call)
216
- return True
217
-
218
- def _get_updated_op_split_symbols(
219
- self, op: QuantumAssignmentOperation, symbol_parts: SymbolParts
220
- ) -> QuantumAssignmentOperation:
221
- return op.model_copy(
222
- update=dict(result_var=self.rewrite(op.result_var, symbol_parts))
223
- )
224
-
225
-
226
- def _validate_naive_inplace_handles(qe: ArithmeticOperation) -> None:
227
- if qe.result_var in qe.var_handles:
228
- raise ClassiqExpansionError(
229
- HANDLE_ERROR_MESSAGE.format(handle_str=str(demangle_handle(qe.result_var)))
230
- )
@@ -1,25 +0,0 @@
1
- from classiq.interface.generator.functions.builtins.internal_operators import (
2
- WITHIN_APPLY_NAME,
3
- )
4
- from classiq.interface.model.within_apply_operation import WithinApply
5
-
6
- from classiq.model_expansions.closure import Closure
7
- from classiq.model_expansions.quantum_operations.emitter import Emitter
8
- from classiq.model_expansions.scope import Scope
9
-
10
-
11
- class WithinApplyEmitter(Emitter[WithinApply]):
12
- def emit(self, within_apply: WithinApply, /) -> None:
13
- within_apply_operation = Closure(
14
- name=WITHIN_APPLY_NAME,
15
- blocks=dict(within=within_apply.compute, apply=within_apply.action),
16
- scope=Scope(parent=self._current_scope),
17
- )
18
- context = self._expand_operation(within_apply_operation)
19
- self.emit_statement(
20
- WithinApply(
21
- compute=context.statements("within"),
22
- action=context.statements("apply"),
23
- source_ref=within_apply.source_ref,
24
- )
25
- )