classiq 0.83.0__py3-none-any.whl → 0.85.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 (103) hide show
  1. classiq/_internals/api_wrapper.py +27 -0
  2. classiq/applications/chemistry/chemistry_model_constructor.py +0 -2
  3. classiq/applications/chemistry/hartree_fock.py +68 -0
  4. classiq/applications/chemistry/mapping.py +85 -0
  5. classiq/applications/chemistry/op_utils.py +79 -0
  6. classiq/applications/chemistry/problems.py +195 -0
  7. classiq/applications/chemistry/ucc.py +109 -0
  8. classiq/applications/chemistry/z2_symmetries.py +368 -0
  9. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +30 -1
  10. classiq/applications/combinatorial_optimization/combinatorial_problem.py +20 -42
  11. classiq/{model_expansions/evaluators → evaluators}/arg_type_match.py +12 -4
  12. classiq/{model_expansions/evaluators → evaluators}/argument_types.py +1 -1
  13. classiq/evaluators/classical_expression.py +53 -0
  14. classiq/{model_expansions/evaluators → evaluators}/classical_type_inference.py +3 -4
  15. classiq/{model_expansions/evaluators → evaluators}/parameter_types.py +17 -15
  16. classiq/execution/__init__.py +12 -1
  17. classiq/execution/execution_session.py +238 -49
  18. classiq/execution/jobs.py +26 -1
  19. classiq/execution/qnn.py +2 -2
  20. classiq/execution/user_budgets.py +39 -0
  21. classiq/interface/_version.py +1 -1
  22. classiq/interface/constants.py +1 -0
  23. classiq/interface/debug_info/debug_info.py +0 -4
  24. classiq/interface/execution/primitives.py +29 -1
  25. classiq/interface/executor/estimate_cost.py +35 -0
  26. classiq/interface/executor/execution_result.py +13 -0
  27. classiq/interface/executor/result.py +116 -1
  28. classiq/interface/executor/user_budget.py +26 -33
  29. classiq/interface/generator/expressions/atomic_expression_functions.py +10 -1
  30. classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +0 -6
  31. classiq/interface/generator/functions/builtins/internal_operators.py +2 -0
  32. classiq/interface/generator/functions/classical_type.py +2 -35
  33. classiq/interface/generator/functions/concrete_types.py +20 -3
  34. classiq/interface/generator/functions/type_modifier.py +0 -19
  35. classiq/interface/generator/generated_circuit_data.py +5 -18
  36. classiq/interface/generator/types/compilation_metadata.py +0 -3
  37. classiq/interface/ide/operation_registry.py +45 -0
  38. classiq/interface/ide/visual_model.py +68 -3
  39. classiq/interface/model/bounds.py +12 -2
  40. classiq/interface/model/model.py +12 -7
  41. classiq/interface/model/port_declaration.py +2 -24
  42. classiq/interface/model/quantum_expressions/arithmetic_operation.py +7 -4
  43. classiq/interface/model/variable_declaration_statement.py +33 -6
  44. classiq/interface/pretty_print/__init__.py +0 -0
  45. classiq/{qmod/native → interface/pretty_print}/expression_to_qmod.py +18 -11
  46. classiq/interface/server/routes.py +4 -0
  47. classiq/model_expansions/atomic_expression_functions_defs.py +47 -6
  48. classiq/model_expansions/function_builder.py +4 -1
  49. classiq/model_expansions/interpreters/base_interpreter.py +3 -3
  50. classiq/model_expansions/interpreters/generative_interpreter.py +16 -1
  51. classiq/model_expansions/quantum_operations/allocate.py +1 -1
  52. classiq/model_expansions/quantum_operations/assignment_result_processor.py +64 -22
  53. classiq/model_expansions/quantum_operations/bind.py +2 -2
  54. classiq/model_expansions/quantum_operations/bounds.py +7 -1
  55. classiq/model_expansions/quantum_operations/call_emitter.py +26 -20
  56. classiq/model_expansions/quantum_operations/classical_var_emitter.py +16 -0
  57. classiq/model_expansions/quantum_operations/variable_decleration.py +31 -11
  58. classiq/model_expansions/scope.py +7 -0
  59. classiq/model_expansions/scope_initialization.py +3 -3
  60. classiq/model_expansions/transformers/model_renamer.py +6 -4
  61. classiq/model_expansions/transformers/type_modifier_inference.py +81 -43
  62. classiq/model_expansions/transformers/var_splitter.py +1 -1
  63. classiq/model_expansions/visitors/symbolic_param_inference.py +2 -3
  64. classiq/open_library/functions/__init__.py +3 -2
  65. classiq/open_library/functions/amplitude_amplification.py +10 -18
  66. classiq/open_library/functions/discrete_sine_cosine_transform.py +5 -5
  67. classiq/open_library/functions/grover.py +14 -6
  68. classiq/open_library/functions/modular_exponentiation.py +22 -20
  69. classiq/open_library/functions/qaoa_penalty.py +8 -1
  70. classiq/open_library/functions/state_preparation.py +18 -32
  71. classiq/qmod/__init__.py +2 -0
  72. classiq/qmod/builtins/enums.py +23 -0
  73. classiq/qmod/builtins/functions/__init__.py +2 -0
  74. classiq/qmod/builtins/functions/exponentiation.py +32 -4
  75. classiq/qmod/builtins/operations.py +65 -1
  76. classiq/qmod/builtins/structs.py +55 -3
  77. classiq/qmod/classical_variable.py +74 -0
  78. classiq/qmod/declaration_inferrer.py +3 -2
  79. classiq/qmod/native/pretty_printer.py +20 -20
  80. classiq/qmod/pretty_print/expression_to_python.py +2 -1
  81. classiq/qmod/pretty_print/pretty_printer.py +35 -21
  82. classiq/qmod/python_classical_type.py +12 -5
  83. classiq/qmod/qfunc.py +2 -19
  84. classiq/qmod/qmod_constant.py +2 -5
  85. classiq/qmod/qmod_parameter.py +2 -5
  86. classiq/qmod/qmod_variable.py +61 -23
  87. classiq/qmod/quantum_expandable.py +5 -3
  88. classiq/qmod/quantum_function.py +49 -4
  89. classiq/qmod/semantics/annotation/qstruct_annotator.py +1 -1
  90. classiq/qmod/semantics/validation/main_validation.py +1 -9
  91. classiq/qmod/symbolic_type.py +2 -1
  92. classiq/qmod/utilities.py +0 -2
  93. classiq/qmod/write_qmod.py +1 -1
  94. {classiq-0.83.0.dist-info → classiq-0.85.0.dist-info}/METADATA +4 -1
  95. {classiq-0.83.0.dist-info → classiq-0.85.0.dist-info}/RECORD +101 -90
  96. classiq/interface/model/quantum_variable_declaration.py +0 -7
  97. classiq/model_expansions/evaluators/classical_expression.py +0 -36
  98. /classiq/{model_expansions/evaluators → evaluators}/__init__.py +0 -0
  99. /classiq/{model_expansions/evaluators → evaluators}/control.py +0 -0
  100. /classiq/{model_expansions → evaluators}/expression_evaluator.py +0 -0
  101. /classiq/{model_expansions/evaluators → evaluators}/quantum_type_utils.py +0 -0
  102. /classiq/{model_expansions/evaluators → evaluators}/type_type_match.py +0 -0
  103. {classiq-0.83.0.dist-info → classiq-0.85.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,368 @@
1
+ from collections.abc import Sequence
2
+ from functools import cached_property
3
+ from typing import TYPE_CHECKING, Any, Optional
4
+
5
+ import numpy as np
6
+ from openfermion.ops.operators.fermion_operator import FermionOperator
7
+ from openfermion.ops.operators.qubit_operator import QubitOperator
8
+ from openfermion.transforms import taper_off_qubits
9
+ from openfermion.utils.commutators import anticommutator, commutator
10
+
11
+ from classiq.interface.exceptions import ClassiqValueError
12
+
13
+ from classiq.applications.chemistry.mapping import FermionToQubitMapper, MappingMethod
14
+ from classiq.applications.chemistry.op_utils import (
15
+ qubit_op_to_xz_matrix,
16
+ xz_matrix_to_qubit_op,
17
+ )
18
+ from classiq.applications.chemistry.problems import FermionHamiltonianProblem
19
+
20
+
21
+ class Z2SymTaperMapper(FermionToQubitMapper):
22
+ """
23
+ Mapper between fermionic operators to qubits operators, using one of the supported
24
+ mapping methods (see `MappingMethod`), and taking advantage of Z2 symmetries in
25
+ order to taper off qubits.
26
+
27
+ Attributes:
28
+ method (MappingMethod): The mapping method.
29
+ generators (tuple[QubitOperator, ...]): Generators representing the Z2
30
+ symmetries.
31
+ x_ops (tuple[QubitOperator, ...]): Single-qubit X operations, such that each
32
+ operation anti-commutes with its matching generator and commutes with all
33
+ other generators.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ generators: Sequence[QubitOperator],
39
+ x_ops: Sequence[QubitOperator],
40
+ method: MappingMethod = MappingMethod.JORDAN_WIGNER,
41
+ sector: Optional[Sequence[int]] = None,
42
+ tol: float = 1e-14,
43
+ ) -> None:
44
+ """
45
+ Initializes a `Z2SymTaperMapper` object from the given configuration.
46
+
47
+ Args:
48
+ generators (Sequence[QubitOperator]): Generators representing the Z2
49
+ symmetries.
50
+ x_ops (Sequence[QubitOperator]): Single-qubit X operations, such that each
51
+ operation anti-commutes with its matching generator and commutes with all
52
+ other generators.
53
+ method (MappingMethod): The mapping method.
54
+ sector: (Sequence[int]): Symmetry sector coefficients, each is 1 or -1.
55
+ If not specified, all coefficients defaults to 1.
56
+ tol (float): Tolerance for trimming off terms.
57
+ """
58
+ super().__init__(method=method)
59
+
60
+ self._validate_symmetries(generators, x_ops)
61
+
62
+ self._generators = generators
63
+ self._x_ops = x_ops
64
+ self._tol = tol
65
+
66
+ self.set_sector(sector or [1] * len(self._generators))
67
+
68
+ @staticmethod
69
+ def _validate_symmetries(
70
+ generators: Sequence[QubitOperator],
71
+ x_ops: Sequence[QubitOperator],
72
+ ) -> None:
73
+ if len(generators) != len(x_ops):
74
+ raise ClassiqValueError(
75
+ "Generators and X operations must have the same length."
76
+ )
77
+
78
+ for i, x_op in enumerate(x_ops):
79
+ for j, gen in enumerate(generators):
80
+ if i == j:
81
+ if anticommutator(x_op, gen) != QubitOperator():
82
+ raise ClassiqValueError(
83
+ f"x_{i}={x_op} and generator_{j}={gen} should anti-commute but don't."
84
+ )
85
+ else:
86
+ if commutator(x_op, gen) != QubitOperator():
87
+ raise ClassiqValueError(
88
+ f"x_{i}={x_op} and generator_{j}={gen} should commute but don't."
89
+ )
90
+
91
+ def set_sector(self, sector: Sequence[int]) -> None:
92
+ """
93
+ Sets the symmetry sector coefficients.
94
+
95
+ Args:
96
+ sector: (Sequence[int]): Symmetry sector coefficients, each is 1 or -1.
97
+ """
98
+ if len(sector) != len(self._generators):
99
+ raise ClassiqValueError(
100
+ "Sector must have the same length as the generators."
101
+ )
102
+ self._sector = sector
103
+
104
+ @property
105
+ def generators(self) -> tuple[QubitOperator, ...]:
106
+ """
107
+ Generators representing the Z2 symmetries.
108
+ """
109
+ return tuple(self._generators)
110
+
111
+ @property
112
+ def x_ops(self) -> tuple[QubitOperator, ...]:
113
+ """
114
+ Single-qubit X operations, such that each operation anti-commutes with its
115
+ matching generator and commutes with all other generators.
116
+ """
117
+ return tuple(self._x_ops)
118
+
119
+ def map(
120
+ self,
121
+ fermion_op: FermionOperator,
122
+ *args: Any,
123
+ is_invariant: bool = False,
124
+ **kwargs: Any,
125
+ ) -> QubitOperator:
126
+ """
127
+ Maps the given fermionic operator to qubits operator by using the
128
+ mapper's method, and subsequently by tapering off qubits according to Z2
129
+ symmetries.
130
+
131
+ Args:
132
+ fermion_op (FermionOperator): A fermionic operator.
133
+ is_invariant (bool): If `False`, the operator is not necessarily in the
134
+ symmetry subspace, and thus gets projected onto it before tapering.
135
+
136
+ Returns:
137
+ The mapped qubits operator.
138
+ """
139
+ qubit_op = super().map(fermion_op)
140
+ sectored_x_ops = [s * x_op for s, x_op in zip(self._sector, self._x_ops)]
141
+
142
+ if not is_invariant:
143
+ qubit_op = _project_operator_to_subspace(qubit_op, self._generators)
144
+
145
+ block_diagonal_op = self._block_diagnolize(qubit_op)
146
+ tapered_op = taper_off_qubits(block_diagonal_op, sectored_x_ops)
147
+ if TYPE_CHECKING:
148
+ assert isinstance(tapered_op, QubitOperator)
149
+ tapered_op.compress(self._tol)
150
+ return tapered_op
151
+
152
+ def get_num_qubits(self, problem: FermionHamiltonianProblem) -> int:
153
+ """
154
+ Gets the number of qubits after mapping the given problem into qubits space.
155
+
156
+ Args:
157
+ problem (FermionHamiltonianProblem): The fermion problem.
158
+
159
+ Returns:
160
+ The number of qubits.
161
+ """
162
+ return super().get_num_qubits(problem) - len(self._generators)
163
+
164
+ @classmethod
165
+ def from_problem(
166
+ cls,
167
+ problem: FermionHamiltonianProblem,
168
+ method: MappingMethod = MappingMethod.JORDAN_WIGNER,
169
+ sector_from_hartree_fock: bool = True,
170
+ tol: float = 1e-14,
171
+ ) -> "Z2SymTaperMapper":
172
+ """
173
+ Initializes a `Z2SymTaperMapper` object from a fermion problem (i.e. computing
174
+ the Z2 symmetries from the problem definition).
175
+
176
+ Args:
177
+ problem (FermionHamiltonianProblem): The fermion problem.
178
+ method (MappingMethod): The mapping method.
179
+ sector_from_hartree_fock (bool): Whether to compute the symmetry sector
180
+ coefficients according to the Hartree-Fock state.
181
+ tol (float): Tolerance for trimming off terms.
182
+
183
+ Returns:
184
+ The Z2 symmetries taper mapper.
185
+ """
186
+ mapper = FermionToQubitMapper(method)
187
+ n_qubits = mapper.get_num_qubits(problem)
188
+
189
+ qubit_op = mapper.map(problem.fermion_hamiltonian)
190
+ qubit_op.compress(tol)
191
+
192
+ generators = _get_z2_symmetries_generators(qubit_op, n_qubits)
193
+ x_ops = _get_x_ops_for_generators(generators, n_qubits)
194
+
195
+ sector: Optional[list[int]] = None
196
+ if sector_from_hartree_fock:
197
+ from classiq.applications.chemistry.hartree_fock import get_hf_state
198
+
199
+ if not (generators[:, :n_qubits] == 0).all():
200
+ raise ClassiqValueError(
201
+ "The Hartree-Fock state is not in the symmetry space spanned by the generators, please set `sector_from_hartree_fock=False`. You can later set the sector manually with `set_sector`."
202
+ )
203
+
204
+ state = get_hf_state(problem, mapper)
205
+ sector = _get_sector_for_basis_state(generators[:, n_qubits:], state)
206
+
207
+ return cls(
208
+ generators=[xz_matrix_to_qubit_op(gen) for gen in generators],
209
+ x_ops=x_ops,
210
+ sector=sector,
211
+ tol=tol,
212
+ method=method,
213
+ )
214
+
215
+ @cached_property
216
+ def _block_diagonalizing_clifford(self) -> QubitOperator:
217
+ op = QubitOperator(())
218
+ for gen, x_op in zip(self._generators, self._x_ops):
219
+ op *= (2 ** (-0.5)) * (x_op + gen)
220
+ return op
221
+
222
+ def _block_diagnolize(self, op: QubitOperator) -> QubitOperator:
223
+ transformed_op = (
224
+ self._block_diagonalizing_clifford * op * self._block_diagonalizing_clifford
225
+ )
226
+ transformed_op.compress(self._tol)
227
+ return transformed_op
228
+
229
+
230
+ def _get_z2_symmetries_generators(op: QubitOperator, n_qubits: int) -> np.ndarray:
231
+ """
232
+ Gets the Z2 symmetries generators of an operator.
233
+
234
+ It can be shown that each vector in the kernel subspace of the operator's XZ matrix,
235
+ after replacing Xs ans Zs, commutes with the operator.
236
+ """
237
+ xz_mat = qubit_op_to_xz_matrix(op, n_qubits)
238
+ kernel = _get_kernel(xz_mat)
239
+ return np.hstack((kernel[:, n_qubits:], kernel[:, :n_qubits]))
240
+
241
+
242
+ def _get_x_ops_for_generators(
243
+ generators: np.ndarray, n_qubits: int
244
+ ) -> list[QubitOperator]:
245
+ """
246
+ Tries to find single-qubit X operations for the given generators, such that each X
247
+ operation anti-commutes with its matching generator and commutes with all the rest.
248
+ """
249
+ x_ops: list[QubitOperator] = []
250
+ for row in range(len(generators)):
251
+
252
+ # we look for a column in the Z-part of the matrix which is populated only with
253
+ # 0s except for a 1 in the current generator: a X operation in this column's
254
+ # qubit will anti-commute with the current generator and commute with all others
255
+ found_col: Optional[int] = None
256
+ for col in range(n_qubits):
257
+ if (
258
+ generators[row, n_qubits + col] == 1
259
+ and np.all(generators[:row, n_qubits + col] == 0)
260
+ and np.all(generators[row + 1 :, n_qubits + col] == 0)
261
+ ):
262
+ found_col = col
263
+ break
264
+ else:
265
+ raise ClassiqValueError(
266
+ "Failed to find X operator for the Z2 symmetry generator."
267
+ )
268
+
269
+ x_ops.append(QubitOperator(((found_col, "X"),)))
270
+
271
+ return x_ops
272
+
273
+
274
+ def _get_sector_for_basis_state(
275
+ generators_z_part: np.ndarray, state: list[bool]
276
+ ) -> list[int]:
277
+ """
278
+ Computes the sector coefficients of a basis state by applying the generators.
279
+ """
280
+ sector: list[int] = []
281
+ for gen in generators_z_part:
282
+ coeff = 1
283
+ for qubit in range(len(state)):
284
+ if state[qubit] and gen[qubit] == 1:
285
+ coeff *= -1
286
+ sector.append(coeff)
287
+ return sector
288
+
289
+
290
+ def _get_kernel(mat: np.ndarray) -> np.ndarray:
291
+ """
292
+ Computes the kernel subspace of the given Z2 matrix.
293
+
294
+ Note: this function changes the given matrix inplace.
295
+ """
296
+ _transform_to_rref(mat)
297
+ return _get_kernel_from_rref(mat)
298
+
299
+
300
+ def _transform_to_rref(mat: np.ndarray) -> None:
301
+ """
302
+ Transforms the given Z2 matrix into RREF (Reduced Row Echelon Form).
303
+
304
+ Note: this function changes the given matrix inplace.
305
+ """
306
+ n_rows, n_cols = mat.shape
307
+ col = 0
308
+
309
+ for row in range(n_rows):
310
+ while col < n_cols and mat[row, col] == 0:
311
+ # find 1 in the current column and swap rows or move to the next column
312
+ for krow in range(row + 1, n_rows):
313
+ if mat[krow, col] == 1:
314
+ mat[[row, krow], col:] = mat[[krow, row], col:]
315
+ break
316
+ else:
317
+ col += 1
318
+
319
+ if col < n_cols:
320
+ # eliminate 1s in current column by XORing their rows with the current row
321
+ curr_row = mat[row, col:]
322
+ mat[:row, col:] ^= np.outer(mat[:row, col], curr_row)
323
+ mat[row + 1 :, col:] ^= np.outer(mat[row + 1 :, col], curr_row)
324
+ col += 1
325
+
326
+
327
+ def _get_kernel_from_rref(mat: np.ndarray) -> np.ndarray:
328
+ """
329
+ Computes the kernel subspace of the given Z2 matrix which is in RREF.
330
+ """
331
+ # remove all-zero rows
332
+ mat = mat[~np.all(mat == 0, axis=1)]
333
+
334
+ n_cols = mat.shape[1]
335
+
336
+ # pivots are indices of columns with leading 1, free columns are the rest
337
+ pivots = np.argmax(mat, axis=1)
338
+ free_cols = np.setdiff1d(np.arange(n_cols), pivots)
339
+
340
+ # for each free column we have a vector in the kernel with 1 in the free column
341
+ # index and possibly 1s in pivots indices
342
+ kernel = np.zeros((free_cols.size, n_cols), dtype=np.int8)
343
+ for vec, free_col in zip(kernel, free_cols):
344
+ vec[free_col] = 1
345
+
346
+ for row, pivot in zip(mat, pivots):
347
+ if row[free_col] == 1:
348
+ vec[pivot] = 1
349
+
350
+ return kernel
351
+
352
+
353
+ def _project_operator_to_subspace(
354
+ op: QubitOperator, generators: Sequence[QubitOperator]
355
+ ) -> QubitOperator:
356
+ """
357
+ Projects the given operator onto the symmetry subspace defined by the given
358
+ generators.
359
+ """
360
+ projected_op = QubitOperator()
361
+ for term, coeff in op.terms.items():
362
+ single_term_op = QubitOperator(term, coeff)
363
+ if all(
364
+ commutator(single_term_op, gen) == QubitOperator() for gen in generators
365
+ ):
366
+ projected_op += single_term_op
367
+
368
+ return projected_op
@@ -3,7 +3,12 @@ from classiq.interface.generator.functions.qmod_python_interface import QmodPySt
3
3
  from classiq.interface.helpers.custom_pydantic_types import PydanticPauliList
4
4
 
5
5
  from classiq.qmod.builtins.enums import Pauli
6
- from classiq.qmod.builtins.structs import PauliTerm
6
+ from classiq.qmod.builtins.structs import (
7
+ IndexedPauli,
8
+ PauliTerm,
9
+ SparsePauliOp,
10
+ SparsePauliTerm,
11
+ )
7
12
 
8
13
 
9
14
  def pauli_operator_to_hamiltonian(pauli_list: PydanticPauliList) -> list[PauliTerm]:
@@ -22,6 +27,30 @@ def pauli_operator_to_hamiltonian(pauli_list: PydanticPauliList) -> list[PauliTe
22
27
  return pauli_terms
23
28
 
24
29
 
30
+ def pauli_operator_to_sparse_hamiltonian(
31
+ pauli_list: PydanticPauliList,
32
+ ) -> SparsePauliOp:
33
+ pauli_terms: list[SparsePauliTerm] = []
34
+ for pauli_term in pauli_list:
35
+ if not isinstance(pauli_term[1], complex) or pauli_term[1].imag != 0:
36
+ raise ClassiqNonNumericCoefficientInPauliError(
37
+ "Coefficient is not a number."
38
+ )
39
+ term = SparsePauliTerm(
40
+ paulis=[ # type:ignore[arg-type]
41
+ IndexedPauli(pauli=Pauli[p], index=i) # type:ignore[arg-type]
42
+ for i, p in enumerate(pauli_term[0][::-1])
43
+ ],
44
+ coefficient=pauli_term[1].real, # type: ignore[arg-type]
45
+ )
46
+ pauli_terms.append(term)
47
+
48
+ return SparsePauliOp(
49
+ terms=pauli_terms, # type:ignore[arg-type]
50
+ num_qubits=len(pauli_list[0][0]), # type:ignore[arg-type]
51
+ )
52
+
53
+
25
54
  def pauli_enum_to_str(pauli: Pauli) -> str:
26
55
  return {
27
56
  Pauli.I: "Pauli.I",
@@ -1,12 +1,11 @@
1
1
  import math
2
2
  import re
3
- from typing import Callable, Optional
3
+ from typing import Callable, Optional, cast
4
4
 
5
5
  import numpy as np
6
6
  import pandas as pd
7
7
  import pyomo.core as pyo
8
8
  import scipy
9
- from tqdm import tqdm
10
9
 
11
10
  from classiq.interface.executor.execution_preferences import ExecutionPreferences
12
11
  from classiq.interface.executor.result import ExecutionDetails
@@ -44,8 +43,8 @@ class CombinatorialProblem:
44
43
  self.num_layers_ = num_layers
45
44
  self.model_ = None
46
45
  self.qprog_ = None
47
- self.es_ = None
48
- self.optimized_params_ = None
46
+ self._es: ExecutionSession | None = None
47
+ self._optimized_params: list[float] | None = None
49
48
  self.params_trace_: list[np.ndarray] = []
50
49
  self.cost_trace_: list = []
51
50
 
@@ -58,8 +57,8 @@ class CombinatorialProblem:
58
57
  return self.params_trace_
59
58
 
60
59
  @property
61
- def optimized_params(self) -> list:
62
- return self.optimized_params_ # type:ignore[return-value]
60
+ def optimized_params(self) -> list[float]:
61
+ return self._optimized_params # type:ignore[return-value]
63
62
 
64
63
  def get_model(
65
64
  self,
@@ -100,22 +99,9 @@ class CombinatorialProblem:
100
99
  ) -> list[float]:
101
100
  if self.qprog_ is None:
102
101
  self.get_qprog()
103
- self.es_ = ExecutionSession(
104
- self.qprog_, execution_preferences # type:ignore[assignment,arg-type]
102
+ _es = ExecutionSession(
103
+ self.qprog_, execution_preferences # type:ignore[arg-type]
105
104
  )
106
- self.params_trace_ = []
107
- self.cost_trace_ = []
108
-
109
- def estimate_cost_wrapper(params: np.ndarray) -> float:
110
- cost = self.es_.estimate_cost( # type:ignore[attr-defined]
111
- lambda state: self.cost_func(state["v"]),
112
- {"params": params.tolist()},
113
- quantile=quantile,
114
- )
115
- self.cost_trace_.append(cost)
116
- self.params_trace_.append(params)
117
- return cost
118
-
119
105
  initial_params = (
120
106
  np.concatenate(
121
107
  (
@@ -125,31 +111,23 @@ class CombinatorialProblem:
125
111
  )
126
112
  * math.pi
127
113
  )
128
-
129
- with tqdm(total=maxiter, desc="Optimization Progress", leave=True) as pbar:
130
-
131
- def _minimze_callback(xk: np.ndarray) -> None:
132
- pbar.update(1) # increment progress bar
133
- self.optimized_params_ = xk.tolist() # save recent optimized value
134
-
135
- self.optimized_params_ = scipy.optimize.minimize(
136
- estimate_cost_wrapper,
137
- callback=_minimze_callback,
138
- x0=initial_params,
139
- method="COBYLA",
140
- options={"maxiter": maxiter},
141
- ).x.tolist()
142
-
143
- return self.optimized_params_ # type:ignore[return-value]
114
+ result = _es.minimize(
115
+ lambda v: self.cost_func(v), # type:ignore[arg-type]
116
+ {"params": initial_params.tolist()},
117
+ maxiter,
118
+ quantile,
119
+ )
120
+ _optimized_params = cast(list[float], result[-1][1]["params"])
121
+ self._optimized_params = _optimized_params
122
+ self._es = _es
123
+ return _optimized_params
144
124
 
145
125
  def sample_uniform(self) -> pd.DataFrame:
146
126
  return self.sample([0] * self.num_layers_ * 2)
147
127
 
148
128
  def sample(self, params: list) -> pd.DataFrame:
149
- assert self.es_ is not None
150
- res = self.es_.sample( # type:ignore[unreachable]
151
- {"params": params}
152
- )
129
+ assert self._es is not None
130
+ res = self._es.sample({"params": params})
153
131
  parsed_result = [
154
132
  {
155
133
  "solution": {
@@ -157,7 +135,7 @@ class CombinatorialProblem:
157
135
  for key, value in sampled.state["v"].items()
158
136
  if not re.match(".*_slack_var_.*", key)
159
137
  },
160
- "probability": sampled.shots / res.num_shots,
138
+ "probability": sampled.shots / res.num_shots, # type:ignore[operator]
161
139
  "cost": self.cost_func(sampled.state["v"]),
162
140
  }
163
141
  for sampled in res.parsed_counts
@@ -25,8 +25,8 @@ from classiq.interface.model.quantum_function_declaration import (
25
25
  AnonQuantumOperandDeclaration,
26
26
  )
27
27
 
28
+ from classiq.evaluators.type_type_match import check_signature_match
28
29
  from classiq.model_expansions.closure import FunctionClosure
29
- from classiq.model_expansions.evaluators.type_type_match import check_signature_match
30
30
  from classiq.model_expansions.scope import Evaluated, QuantumVariable
31
31
  from classiq.qmod.model_state_container import QMODULE
32
32
  from classiq.qmod.qmod_parameter import CInt, get_qmod_type
@@ -69,7 +69,7 @@ def check_arg_type_match(
69
69
  error_message = (
70
70
  message_prefix + f"expected {_resolve_type_name(parameter.classical_type)}"
71
71
  )
72
- _check_classical_type_match(argument, parameter, error_message)
72
+ _check_classical_type_match(argument, parameter, error_message, function_name)
73
73
  else:
74
74
  raise ClassiqExpansionError(
75
75
  f"unexpected parameter declaration type: {type(parameter).__name__}"
@@ -118,7 +118,10 @@ def _check_operand_type_match(
118
118
 
119
119
 
120
120
  def _check_classical_type_match(
121
- argument: Any, parameter: AnonClassicalParameterDeclaration, error_message: str
121
+ argument: Any,
122
+ parameter: AnonClassicalParameterDeclaration,
123
+ error_message: str,
124
+ function_name: str,
122
125
  ) -> None:
123
126
  classical_type = parameter.classical_type
124
127
  type_name = _resolve_type_name(classical_type)
@@ -132,12 +135,17 @@ def _check_classical_type_match(
132
135
  )
133
136
  arg_is_qvar = isinstance(argument, QmodSizedProxy)
134
137
  arg_is_builtin = argument.__class__.__module__ == "builtins"
138
+ arg_is_int = isinstance(argument, int)
135
139
  arg_is_enum = isinstance(argument, Enum)
136
140
  arg_is_struct = isinstance(argument, QmodStructInstance)
137
141
  arg_struct_name = None if not arg_is_struct else argument.struct_declaration.name
142
+ # FIXME: Remove suzuki_trotter overloading (CLS-2912)
143
+ if function_name == "suzuki_trotter" and parameter.name == "pauli_operator":
144
+ return
138
145
  if (
139
146
  arg_is_qvar
140
- or (arg_is_builtin and (type_is_struct or type_is_enum))
147
+ or (arg_is_builtin and type_is_struct)
148
+ or (arg_is_builtin and not arg_is_int and type_is_enum)
141
149
  or (arg_is_struct and (not type_is_struct or arg_struct_name != type_name))
142
150
  or (
143
151
  arg_is_enum
@@ -8,7 +8,7 @@ from classiq.interface.model.port_declaration import AnonPortDeclaration
8
8
  from classiq.interface.model.quantum_function_declaration import AnonPositionalArg
9
9
  from classiq.interface.model.quantum_type import QuantumNumeric
10
10
 
11
- from classiq.model_expansions.evaluators.quantum_type_utils import copy_type_information
11
+ from classiq.evaluators.quantum_type_utils import copy_type_information
12
12
  from classiq.model_expansions.scope import Evaluated, QuantumVariable
13
13
 
14
14
 
@@ -0,0 +1,53 @@
1
+ from typing import get_args
2
+
3
+ from classiq.interface.generator.expressions.evaluated_expression import (
4
+ EvaluatedExpression,
5
+ )
6
+ from classiq.interface.generator.expressions.expression import Expression
7
+ from classiq.interface.generator.expressions.expression_types import ExpressionValue
8
+ from classiq.interface.generator.expressions.proxies.classical.any_classical_value import (
9
+ AnyClassicalValue,
10
+ )
11
+ from classiq.interface.model.handle_binding import HandleBinding
12
+
13
+ from classiq.evaluators.expression_evaluator import evaluate
14
+ from classiq.model_expansions.scope import (
15
+ ClassicalSymbol,
16
+ Evaluated,
17
+ QuantumSymbol,
18
+ Scope,
19
+ )
20
+
21
+
22
+ def evaluate_classical_expression(expr: Expression, scope: Scope) -> Evaluated:
23
+ all_symbols = scope.items()
24
+ locals_dict = (
25
+ {
26
+ name: EvaluatedExpression(value=evaluated.value)
27
+ for name, evaluated in all_symbols
28
+ if isinstance(evaluated.value, get_args(ExpressionValue))
29
+ }
30
+ | {
31
+ name: EvaluatedExpression(
32
+ value=(
33
+ evaluated.value.quantum_type.get_proxy(HandleBinding(name=name))
34
+ if evaluated.value.quantum_type.is_evaluated
35
+ else AnyClassicalValue(name)
36
+ )
37
+ )
38
+ for name, evaluated in all_symbols
39
+ if isinstance(evaluated.value, QuantumSymbol)
40
+ }
41
+ | {
42
+ name: EvaluatedExpression(
43
+ value=evaluated.value.classical_type.get_classical_proxy(
44
+ HandleBinding(name=name)
45
+ )
46
+ )
47
+ for name, evaluated in all_symbols
48
+ if isinstance(evaluated.value, ClassicalSymbol)
49
+ }
50
+ )
51
+
52
+ ret = evaluate(expr, locals_dict)
53
+ return Evaluated(value=ret.value)
@@ -17,7 +17,6 @@ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_insta
17
17
  )
18
18
  from classiq.interface.generator.functions.classical_type import (
19
19
  ClassicalArray,
20
- ClassicalList,
21
20
  ClassicalTuple,
22
21
  ClassicalType,
23
22
  )
@@ -29,7 +28,7 @@ from classiq.interface.helpers.backward_compatibility import zip_strict
29
28
  def infer_classical_type(val: Any, classical_type: ClassicalType) -> ClassicalType:
30
29
  if isinstance(classical_type, TypeName):
31
30
  return _infer_classical_struct_type(val, classical_type)
32
- if isinstance(classical_type, (ClassicalArray, ClassicalList, ClassicalTuple)):
31
+ if isinstance(classical_type, (ClassicalArray, ClassicalTuple)):
33
32
  return _infer_classical_array_type(val, classical_type)
34
33
  return classical_type
35
34
 
@@ -58,7 +57,7 @@ def _infer_classical_struct_type(val: Any, classical_type: TypeName) -> Classica
58
57
 
59
58
 
60
59
  def _infer_classical_array_type(
61
- val: Any, classical_type: Union[ClassicalArray, ClassicalList, ClassicalTuple]
60
+ val: Any, classical_type: Union[ClassicalArray, ClassicalTuple]
62
61
  ) -> ClassicalType:
63
62
  if isinstance(val, ClassicalSequenceProxy):
64
63
  val_length = val.length
@@ -102,7 +101,7 @@ def _infer_inner_array_types(
102
101
  ),
103
102
  )
104
103
  if TYPE_CHECKING:
105
- assert isinstance(classical_type, (ClassicalList, ClassicalArray))
104
+ assert isinstance(classical_type, ClassicalArray)
106
105
  if _is_int(val_length) and val_length != 0:
107
106
  return ClassicalTuple(
108
107
  element_types=(