classiq 0.86.0__py3-none-any.whl → 0.87.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 (96) hide show
  1. classiq/__init__.py +2 -0
  2. classiq/applications/chemistry/hartree_fock.py +5 -1
  3. classiq/applications/chemistry/op_utils.py +2 -2
  4. classiq/applications/combinatorial_helpers/encoding_mapping.py +11 -15
  5. classiq/applications/combinatorial_helpers/encoding_utils.py +6 -6
  6. classiq/applications/combinatorial_helpers/memory.py +4 -4
  7. classiq/applications/combinatorial_helpers/optimization_model.py +5 -5
  8. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +6 -10
  9. classiq/applications/combinatorial_helpers/pyomo_utils.py +27 -26
  10. classiq/applications/combinatorial_helpers/sympy_utils.py +2 -2
  11. classiq/applications/combinatorial_helpers/transformations/encoding.py +4 -6
  12. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +4 -4
  13. classiq/applications/combinatorial_helpers/transformations/ising_converter.py +2 -2
  14. classiq/applications/combinatorial_helpers/transformations/penalty_support.py +3 -3
  15. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -0
  16. classiq/applications/hamiltonian/pauli_decomposition.py +33 -1
  17. classiq/evaluators/argument_types.py +15 -6
  18. classiq/evaluators/parameter_types.py +43 -39
  19. classiq/evaluators/qmod_annotated_expression.py +88 -11
  20. classiq/evaluators/qmod_expression_visitors/out_of_place_node_transformer.py +19 -0
  21. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +54 -11
  22. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +40 -25
  23. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +29 -59
  24. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +12 -5
  25. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +175 -28
  26. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +21 -14
  27. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +9 -5
  28. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +20 -1
  29. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +97 -0
  30. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +11 -26
  31. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +56 -0
  32. classiq/evaluators/qmod_node_evaluators/piecewise_evaluation.py +40 -0
  33. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +2 -3
  34. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +48 -21
  35. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +53 -9
  36. classiq/evaluators/qmod_node_evaluators/utils.py +27 -5
  37. classiq/evaluators/qmod_type_inference/classical_type_inference.py +188 -0
  38. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +292 -0
  39. classiq/evaluators/quantum_type_utils.py +0 -131
  40. classiq/execution/execution_session.py +1 -1
  41. classiq/execution/qnn.py +4 -1
  42. classiq/execution/user_budgets.py +1 -1
  43. classiq/interface/_version.py +1 -1
  44. classiq/interface/backend/backend_preferences.py +10 -30
  45. classiq/interface/backend/quantum_backend_providers.py +63 -52
  46. classiq/interface/generator/arith/binary_ops.py +107 -115
  47. classiq/interface/generator/arith/extremum_operations.py +33 -45
  48. classiq/interface/generator/arith/number_utils.py +4 -1
  49. classiq/interface/generator/circuit_code/types_and_constants.py +0 -9
  50. classiq/interface/generator/compiler_keywords.py +2 -0
  51. classiq/interface/generator/function_param_list.py +133 -5
  52. classiq/interface/generator/functions/classical_type.py +59 -2
  53. classiq/interface/generator/functions/qmod_python_interface.py +15 -0
  54. classiq/interface/generator/functions/type_name.py +6 -0
  55. classiq/interface/generator/model/preferences/preferences.py +1 -17
  56. classiq/interface/generator/quantum_program.py +1 -13
  57. classiq/interface/helpers/model_normalizer.py +2 -2
  58. classiq/interface/helpers/text_utils.py +7 -2
  59. classiq/interface/interface_version.py +1 -1
  60. classiq/interface/model/classical_if.py +40 -0
  61. classiq/interface/model/handle_binding.py +28 -16
  62. classiq/interface/model/quantum_type.py +61 -2
  63. classiq/interface/pretty_print/expression_to_qmod.py +24 -11
  64. classiq/interface/pyomo_extension/__init__.py +0 -4
  65. classiq/interface/pyomo_extension/pyomo_sympy_bimap.py +2 -2
  66. classiq/model_expansions/arithmetic.py +43 -1
  67. classiq/model_expansions/arithmetic_compute_result_attrs.py +255 -0
  68. classiq/model_expansions/capturing/captured_vars.py +2 -5
  69. classiq/model_expansions/quantum_operations/allocate.py +22 -15
  70. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -3
  71. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -71
  72. classiq/model_expansions/quantum_operations/bind.py +15 -7
  73. classiq/model_expansions/quantum_operations/call_emitter.py +2 -10
  74. classiq/model_expansions/quantum_operations/classical_var_emitter.py +6 -0
  75. classiq/open_library/functions/__init__.py +3 -0
  76. classiq/open_library/functions/lcu.py +117 -0
  77. classiq/qmod/builtins/enums.py +2 -2
  78. classiq/qmod/builtins/structs.py +33 -18
  79. classiq/qmod/pretty_print/expression_to_python.py +7 -9
  80. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/METADATA +3 -3
  81. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/RECORD +83 -89
  82. classiq/interface/generator/amplitude_estimation.py +0 -34
  83. classiq/interface/generator/function_param_list_without_self_reference.py +0 -160
  84. classiq/interface/generator/grover_diffuser.py +0 -93
  85. classiq/interface/generator/grover_operator.py +0 -106
  86. classiq/interface/generator/oracles/__init__.py +0 -3
  87. classiq/interface/generator/oracles/arithmetic_oracle.py +0 -82
  88. classiq/interface/generator/oracles/custom_oracle.py +0 -65
  89. classiq/interface/generator/oracles/oracle_abc.py +0 -76
  90. classiq/interface/generator/oracles/oracle_function_param_list.py +0 -6
  91. classiq/interface/generator/piecewise_linear_amplitude_loading.py +0 -165
  92. classiq/interface/generator/qpe.py +0 -169
  93. classiq/interface/grover/grover_modelling_params.py +0 -13
  94. classiq/model_expansions/transformers/var_splitter.py +0 -224
  95. /classiq/{interface/grover → evaluators/qmod_type_inference}/__init__.py +0 -0
  96. {classiq-0.86.0.dist-info → classiq-0.87.0.dist-info}/WHEEL +0 -0
@@ -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
@@ -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",
@@ -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
+ _load_phases,
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
+ _load_phases(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
+ )
@@ -188,7 +188,7 @@ class Pauli(IntEnum):
188
188
  )
189
189
 
190
190
  return SparsePauliOp(
191
- terms=[ # type:ignore[arg-type]
191
+ terms=[
192
192
  SparsePauliTerm(
193
193
  paulis=[ # type:ignore[arg-type]
194
194
  IndexedPauli(pauli=self, index=index) # type:ignore[arg-type]
@@ -196,7 +196,7 @@ class Pauli(IntEnum):
196
196
  coefficient=1.0, # type:ignore[arg-type]
197
197
  )
198
198
  ],
199
- num_qubits=index + 1, # type:ignore[arg-type]
199
+ num_qubits=index + 1,
200
200
  )
201
201
 
202
202
 
@@ -63,27 +63,31 @@ class SparsePauliOp:
63
63
  num_qubits (CInt): The number of qubits in the Hamiltonian.
64
64
  """
65
65
 
66
- terms: CArray[SparsePauliTerm]
67
- num_qubits: CInt
66
+ terms: list[SparsePauliTerm]
67
+ num_qubits: int
68
68
 
69
69
  def __mul__(self, obj: Union[float, "SparsePauliOp"]) -> "SparsePauliOp":
70
70
  if isinstance(obj, (int, float, complex)):
71
71
  return SparsePauliOp(
72
- terms=[ # type:ignore[arg-type]
72
+ terms=[
73
73
  SparsePauliTerm(
74
74
  paulis=term.paulis,
75
- coefficient=obj * term.coefficient,
75
+ coefficient=obj * term.coefficient, # type:ignore[arg-type]
76
76
  )
77
- for term in self.terms # type:ignore[attr-defined]
77
+ for term in self.terms
78
78
  ],
79
79
  num_qubits=self.num_qubits,
80
80
  )
81
- if len(self.terms) != 1 or len(obj.terms) != 1: # type:ignore[arg-type]
81
+ if len(self.terms) != 1 or len(obj.terms) != 1:
82
82
  raise ClassiqValueError("Cannot attach a pauli to multiple pauli terms")
83
83
  existing_indices = {
84
- indexed_pauli.index for indexed_pauli in self.terms[0].paulis
84
+ indexed_pauli.index
85
+ for indexed_pauli in self.terms[0].paulis # type:ignore[attr-defined]
86
+ }
87
+ added_indices = {
88
+ indexed_pauli.index
89
+ for indexed_pauli in obj.terms[0].paulis # type:ignore[attr-defined]
85
90
  }
86
- added_indices = {indexed_pauli.index for indexed_pauli in obj.terms[0].paulis}
87
91
  overlapping_indices = sorted(existing_indices.intersection(added_indices))
88
92
  if len(overlapping_indices):
89
93
  raise ClassiqValueError(
@@ -93,15 +97,15 @@ class SparsePauliOp:
93
97
  f"already assigned"
94
98
  )
95
99
  return SparsePauliOp(
96
- terms=[ # type:ignore[arg-type]
100
+ terms=[
97
101
  SparsePauliTerm(
98
- paulis=self.terms[0].paulis + obj.terms[0].paulis,
99
- coefficient=self.terms[0].coefficient * obj.terms[0].coefficient,
102
+ paulis=self.terms[0].paulis
103
+ + obj.terms[0].paulis, # type:ignore[arg-type]
104
+ coefficient=self.terms[0].coefficient
105
+ * obj.terms[0].coefficient, # type:ignore[arg-type]
100
106
  )
101
107
  ],
102
- num_qubits=max(
103
- self.num_qubits, obj.num_qubits # type:ignore[call-overload]
104
- ),
108
+ num_qubits=max(self.num_qubits, obj.num_qubits),
105
109
  )
106
110
 
107
111
  def __rmul__(self, obj: Union[float, "SparsePauliOp"]) -> "SparsePauliOp":
@@ -109,10 +113,21 @@ class SparsePauliOp:
109
113
 
110
114
  def __add__(self, other: "SparsePauliOp") -> "SparsePauliOp":
111
115
  return SparsePauliOp(
112
- terms=self.terms + other.terms, # type:ignore[arg-type]
113
- num_qubits=max(
114
- self.num_qubits, other.num_qubits # type:ignore[call-overload]
115
- ),
116
+ terms=self.terms + other.terms,
117
+ num_qubits=max(self.num_qubits, other.num_qubits),
118
+ )
119
+
120
+ def __sub__(self, other: "SparsePauliOp") -> "SparsePauliOp":
121
+ return self + -1.0 * other
122
+
123
+ def __str__(self) -> str:
124
+ return " + ".join(
125
+ (f"{term.coefficient}*" if term.coefficient != 1 else "")
126
+ + "*".join(
127
+ f"Pauli.{indexed_pauli.pauli.name}({indexed_pauli.index})"
128
+ for indexed_pauli in term.paulis # type:ignore[attr-defined]
129
+ )
130
+ for term in self.terms
116
131
  )
117
132
 
118
133
 
@@ -71,12 +71,7 @@ class ASTToQMODCode(ast.NodeVisitor):
71
71
  return self.indent.join(self.visit(child) for child in node.body)
72
72
 
73
73
  def visit_Attribute(self, node: ast.Attribute) -> str:
74
- if not isinstance(node.value, ast.Name) or not isinstance(node.attr, str):
75
- raise AssertionError("Error parsing enum attribute access")
76
- if not (IDENTIFIER.match(node.value.id) and IDENTIFIER.match(node.attr)):
77
- raise AssertionError("Error parsing enum attribute access")
78
- self._handle_imports(node.value.id)
79
- return f"{node.value.id!s}.{node.attr!s}"
74
+ return f"{self.visit(node.value)}.{node.attr}"
80
75
 
81
76
  def visit_Name(self, node: ast.Name) -> str:
82
77
  self._handle_imports(node.id, True)
@@ -159,9 +154,12 @@ class ASTToQMODCode(ast.NodeVisitor):
159
154
  )
160
155
  return f"{self.visit(node.args[0])}({initializer_list})"
161
156
  else:
162
- return "{}({})".format(
163
- func, ", ".join(self._cleaned_ast_to_code(arg) for arg in node.args)
164
- )
157
+ args = [self._cleaned_ast_to_code(arg) for arg in node.args]
158
+ kwargs = [
159
+ f"{kwarg.arg}={self._cleaned_ast_to_code(kwarg.value)}"
160
+ for kwarg in node.keywords
161
+ ]
162
+ return "{}({})".format(func, ", ".join(args + kwargs))
165
163
 
166
164
  def visit_Expr(self, node: ast.Expr) -> str:
167
165
  return self._cleaned_ast_to_code(node.value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: classiq
3
- Version: 0.86.0
3
+ Version: 0.87.0
4
4
  Summary: Classiq's Python SDK for quantum computing
5
5
  License: Proprietary
6
6
  Keywords: quantum computing,quantum circuits,quantum algorithms,QAD,QDL
@@ -30,14 +30,14 @@ Provides-Extra: analyzer-sdk
30
30
  Provides-Extra: chemistry
31
31
  Provides-Extra: qml
32
32
  Requires-Dist: ConfigArgParse (>=1.5.3,<2.0.0)
33
- Requires-Dist: Pyomo (>=6.5,<6.6)
33
+ Requires-Dist: Pyomo (>=6.9,<6.10)
34
34
  Requires-Dist: black (>=24.0,<25.0)
35
35
  Requires-Dist: httpx (>=0.23.0,<1)
36
36
  Requires-Dist: ipywidgets ; extra == "analyzer-sdk"
37
37
  Requires-Dist: jupyterlab ; extra == "analyzer-sdk"
38
38
  Requires-Dist: keyring (>=23.5.0,<24.0.0)
39
39
  Requires-Dist: matplotlib (>=3.4.3,<4.0.0)
40
- Requires-Dist: networkx (>=2.5.1,<3.0.0)
40
+ Requires-Dist: networkx (>=3.2.0,<4.0.0)
41
41
  Requires-Dist: notebook ; extra == "analyzer-sdk"
42
42
  Requires-Dist: numexpr (>=2.7.3,<3.0.0)
43
43
  Requires-Dist: numpy (>=1.20.1,<2.0.0) ; python_version < "3.12"