classiq 0.82.1__py3-none-any.whl → 0.84.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.
- classiq/_internals/api_wrapper.py +27 -0
- classiq/applications/chemistry/chemistry_model_constructor.py +2 -4
- classiq/applications/chemistry/hartree_fock.py +68 -0
- classiq/applications/chemistry/mapping.py +85 -0
- classiq/applications/chemistry/op_utils.py +79 -0
- classiq/applications/chemistry/problems.py +195 -0
- classiq/applications/chemistry/ucc.py +109 -0
- classiq/applications/chemistry/z2_symmetries.py +368 -0
- classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +30 -1
- classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +2 -2
- classiq/{model_expansions/evaluators → evaluators}/arg_type_match.py +12 -4
- classiq/{model_expansions/evaluators → evaluators}/argument_types.py +3 -3
- classiq/{model_expansions/evaluators → evaluators}/classical_expression.py +1 -1
- classiq/{model_expansions/evaluators → evaluators}/classical_type_inference.py +3 -4
- classiq/{model_expansions/evaluators → evaluators}/parameter_types.py +17 -15
- classiq/execution/__init__.py +12 -1
- classiq/execution/execution_session.py +189 -43
- classiq/execution/jobs.py +26 -1
- classiq/execution/qnn.py +2 -2
- classiq/execution/user_budgets.py +39 -0
- classiq/interface/_version.py +1 -1
- classiq/interface/constants.py +1 -0
- classiq/interface/execution/primitives.py +29 -1
- classiq/interface/executor/estimate_cost.py +35 -0
- classiq/interface/executor/execution_result.py +13 -0
- classiq/interface/executor/result.py +116 -1
- classiq/interface/executor/user_budget.py +26 -33
- classiq/interface/generator/application_apis/finance_declarations.py +3 -3
- classiq/interface/generator/expressions/atomic_expression_functions.py +11 -3
- classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +0 -6
- classiq/interface/generator/functions/classical_type.py +2 -35
- classiq/interface/generator/functions/concrete_types.py +0 -3
- classiq/interface/generator/functions/type_modifier.py +22 -0
- classiq/interface/generator/generated_circuit_data.py +5 -16
- classiq/interface/generator/model/model.py +8 -0
- classiq/interface/generator/quantum_program.py +0 -13
- classiq/interface/generator/types/compilation_metadata.py +0 -3
- classiq/interface/helpers/model_normalizer.py +2 -2
- classiq/interface/ide/visual_model.py +6 -2
- classiq/interface/model/model.py +12 -7
- classiq/interface/model/port_declaration.py +4 -2
- classiq/interface/pretty_print/__init__.py +0 -0
- classiq/{qmod/native → interface/pretty_print}/expression_to_qmod.py +18 -11
- classiq/interface/server/routes.py +4 -0
- classiq/model_expansions/atomic_expression_functions_defs.py +42 -5
- classiq/model_expansions/capturing/captured_vars.py +21 -8
- classiq/model_expansions/interpreters/base_interpreter.py +3 -3
- classiq/model_expansions/quantum_operations/allocate.py +1 -1
- classiq/model_expansions/quantum_operations/assignment_result_processor.py +1 -1
- classiq/model_expansions/quantum_operations/bind.py +2 -2
- classiq/model_expansions/quantum_operations/call_emitter.py +42 -36
- classiq/model_expansions/quantum_operations/variable_decleration.py +1 -1
- classiq/model_expansions/scope_initialization.py +3 -3
- classiq/model_expansions/transformers/model_renamer.py +16 -5
- classiq/model_expansions/transformers/{type_qualifier_inference.py → type_modifier_inference.py} +134 -100
- classiq/model_expansions/visitors/symbolic_param_inference.py +10 -7
- classiq/open_library/functions/__init__.py +3 -0
- classiq/open_library/functions/amplitude_amplification.py +10 -18
- classiq/open_library/functions/discrete_sine_cosine_transform.py +5 -5
- classiq/open_library/functions/grover.py +14 -6
- classiq/open_library/functions/modular_exponentiation.py +22 -20
- classiq/open_library/functions/state_preparation.py +18 -1
- classiq/qmod/__init__.py +2 -2
- classiq/qmod/builtins/enums.py +23 -0
- classiq/qmod/builtins/functions/__init__.py +2 -0
- classiq/qmod/builtins/functions/allocation.py +2 -2
- classiq/qmod/builtins/functions/arithmetic.py +16 -8
- classiq/qmod/builtins/functions/exponentiation.py +32 -4
- classiq/qmod/builtins/functions/standard_gates.py +7 -7
- classiq/qmod/builtins/structs.py +55 -3
- classiq/qmod/declaration_inferrer.py +8 -7
- classiq/qmod/native/pretty_printer.py +7 -11
- classiq/qmod/pretty_print/expression_to_python.py +2 -1
- classiq/qmod/pretty_print/pretty_printer.py +7 -12
- classiq/qmod/python_classical_type.py +12 -5
- classiq/qmod/qfunc.py +1 -1
- classiq/qmod/qmod_constant.py +2 -5
- classiq/qmod/qmod_parameter.py +2 -5
- classiq/qmod/qmod_variable.py +66 -25
- classiq/qmod/quantum_expandable.py +4 -2
- classiq/qmod/quantum_function.py +7 -2
- classiq/qmod/semantics/annotation/qstruct_annotator.py +1 -1
- classiq/qmod/semantics/validation/main_validation.py +1 -9
- classiq/qmod/semantics/validation/type_hints.py +9 -9
- classiq/qmod/utilities.py +0 -2
- classiq/qmod/write_qmod.py +1 -1
- classiq/synthesis.py +0 -2
- {classiq-0.82.1.dist-info → classiq-0.84.0.dist-info}/METADATA +4 -1
- {classiq-0.82.1.dist-info → classiq-0.84.0.dist-info}/RECORD +95 -86
- classiq/interface/generator/functions/type_qualifier.py +0 -22
- /classiq/{model_expansions/evaluators → evaluators}/__init__.py +0 -0
- /classiq/{model_expansions/evaluators → evaluators}/control.py +0 -0
- /classiq/{model_expansions → evaluators}/expression_evaluator.py +0 -0
- /classiq/{model_expansions/evaluators → evaluators}/quantum_type_utils.py +0 -0
- /classiq/{model_expansions/evaluators → evaluators}/type_type_match.py +0 -0
- {classiq-0.82.1.dist-info → classiq-0.84.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
|
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",
|
classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py
CHANGED
@@ -12,8 +12,8 @@ from classiq.interface.generator.functions.classical_type import (
|
|
12
12
|
from classiq.interface.generator.functions.port_declaration import (
|
13
13
|
PortDeclarationDirection,
|
14
14
|
)
|
15
|
+
from classiq.interface.generator.functions.type_modifier import TypeModifier
|
15
16
|
from classiq.interface.generator.functions.type_name import Struct
|
16
|
-
from classiq.interface.generator.functions.type_qualifier import TypeQualifier
|
17
17
|
from classiq.interface.model.allocate import Allocate
|
18
18
|
from classiq.interface.model.classical_parameter_declaration import (
|
19
19
|
ClassicalParameterDeclaration,
|
@@ -96,7 +96,7 @@ def construct_combi_opt_py_model(
|
|
96
96
|
length=Expression(expr=f"{len_hamiltonian}"),
|
97
97
|
),
|
98
98
|
direction=PortDeclarationDirection.Output,
|
99
|
-
|
99
|
+
type_modifier=TypeModifier.Mutable,
|
100
100
|
),
|
101
101
|
],
|
102
102
|
body=[
|
@@ -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,
|
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
|
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
|
@@ -3,12 +3,12 @@ from collections.abc import Sequence
|
|
3
3
|
from classiq.interface.generator.functions.port_declaration import (
|
4
4
|
PortDeclarationDirection,
|
5
5
|
)
|
6
|
-
from classiq.interface.generator.functions.
|
6
|
+
from classiq.interface.generator.functions.type_modifier import TypeModifier
|
7
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.
|
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
|
|
@@ -56,7 +56,7 @@ def handle_args_numeric_bounds(
|
|
56
56
|
|
57
57
|
if (
|
58
58
|
parameter.direction != PortDeclarationDirection.Output
|
59
|
-
and parameter.
|
59
|
+
and parameter.type_modifier != TypeModifier.Const
|
60
60
|
and isinstance(argument_as_quantum_symbol.quantum_type, QuantumNumeric)
|
61
61
|
):
|
62
62
|
argument_as_quantum_symbol.quantum_type.reset_bounds()
|
@@ -10,7 +10,7 @@ from classiq.interface.generator.expressions.proxies.classical.any_classical_val
|
|
10
10
|
)
|
11
11
|
from classiq.interface.model.handle_binding import HandleBinding
|
12
12
|
|
13
|
-
from classiq.
|
13
|
+
from classiq.evaluators.expression_evaluator import evaluate
|
14
14
|
from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
|
15
15
|
|
16
16
|
|
@@ -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,
|
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,
|
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,
|
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=(
|
@@ -12,7 +12,6 @@ from classiq.interface.generator.expressions.proxies.classical.any_classical_val
|
|
12
12
|
)
|
13
13
|
from classiq.interface.generator.functions.classical_type import (
|
14
14
|
ClassicalArray,
|
15
|
-
ClassicalList,
|
16
15
|
ClassicalTuple,
|
17
16
|
ClassicalType,
|
18
17
|
)
|
@@ -39,20 +38,20 @@ from classiq.interface.model.quantum_type import (
|
|
39
38
|
QuantumType,
|
40
39
|
)
|
41
40
|
|
42
|
-
from classiq.
|
43
|
-
from classiq.
|
44
|
-
from classiq.model_expansions.evaluators.classical_expression import (
|
41
|
+
from classiq.evaluators.arg_type_match import check_type_match
|
42
|
+
from classiq.evaluators.classical_expression import (
|
45
43
|
evaluate_classical_expression,
|
46
44
|
)
|
47
|
-
from classiq.
|
45
|
+
from classiq.evaluators.classical_type_inference import (
|
48
46
|
infer_classical_type,
|
49
47
|
)
|
50
|
-
from classiq.
|
48
|
+
from classiq.evaluators.quantum_type_utils import (
|
51
49
|
copy_type_information,
|
52
50
|
set_element_type,
|
53
51
|
set_length,
|
54
52
|
set_size,
|
55
53
|
)
|
54
|
+
from classiq.model_expansions.closure import FunctionClosure
|
56
55
|
from classiq.model_expansions.scope import (
|
57
56
|
Evaluated,
|
58
57
|
QuantumSymbol,
|
@@ -81,7 +80,10 @@ def evaluate_parameter_types_from_args(
|
|
81
80
|
|
82
81
|
return [
|
83
82
|
_evaluate_type_from_arg(
|
84
|
-
parameter,
|
83
|
+
parameter,
|
84
|
+
argument,
|
85
|
+
Scope(parent=closure.scope | signature_scope),
|
86
|
+
closure.name,
|
85
87
|
)
|
86
88
|
for parameter, argument in zip(parameters, arguments)
|
87
89
|
]
|
@@ -142,8 +144,14 @@ def _cast(
|
|
142
144
|
|
143
145
|
|
144
146
|
def _evaluate_type_from_arg(
|
145
|
-
parameter: PositionalArg,
|
147
|
+
parameter: PositionalArg,
|
148
|
+
argument: Evaluated,
|
149
|
+
inner_scope: Scope,
|
150
|
+
function_name: str,
|
146
151
|
) -> PositionalArg:
|
152
|
+
# FIXME: Remove suzuki_trotter overloading (CLS-2912)
|
153
|
+
if function_name == "suzuki_trotter" and parameter.name == "pauli_operator":
|
154
|
+
return parameter
|
147
155
|
if isinstance(parameter, ClassicalParameterDeclaration):
|
148
156
|
updated_classical_type = evaluate_type_in_classical_symbol(
|
149
157
|
parameter.classical_type.model_copy(), inner_scope, parameter.name
|
@@ -310,13 +318,7 @@ def evaluate_type_in_classical_symbol(
|
|
310
318
|
type_to_update: ClassicalType, scope: Scope, param_name: str
|
311
319
|
) -> ClassicalType:
|
312
320
|
updated_type: ClassicalType
|
313
|
-
if isinstance(type_to_update,
|
314
|
-
updated_type = ClassicalArray(
|
315
|
-
element_type=evaluate_type_in_classical_symbol(
|
316
|
-
type_to_update.element_type, scope, param_name
|
317
|
-
)
|
318
|
-
)
|
319
|
-
elif isinstance(type_to_update, ClassicalArray):
|
321
|
+
if isinstance(type_to_update, ClassicalArray):
|
320
322
|
length = type_to_update.length
|
321
323
|
if length is not None:
|
322
324
|
new_length = _eval_expr(
|
classiq/execution/__init__.py
CHANGED
@@ -11,7 +11,14 @@ from .execution_session import ExecutionSession
|
|
11
11
|
from .iqcc import generate_iqcc_token, generate_iqcc_token_async
|
12
12
|
from .jobs import ExecutionJob, get_execution_jobs, get_execution_jobs_async
|
13
13
|
from .qnn import execute_qnn
|
14
|
-
from .user_budgets import
|
14
|
+
from .user_budgets import (
|
15
|
+
clear_budget_limit,
|
16
|
+
clear_budget_limit_async,
|
17
|
+
get_budget,
|
18
|
+
get_budget_async,
|
19
|
+
set_budget_limit,
|
20
|
+
set_budget_limit_async,
|
21
|
+
)
|
15
22
|
|
16
23
|
__all__ = (
|
17
24
|
_be_all
|
@@ -30,6 +37,10 @@ __all__ = (
|
|
30
37
|
"generate_iqcc_token_async",
|
31
38
|
"get_budget",
|
32
39
|
"get_budget_async",
|
40
|
+
"set_budget_limit",
|
41
|
+
"set_budget_limit_async",
|
42
|
+
"clear_budget_limit",
|
43
|
+
"clear_budget_limit_async",
|
33
44
|
]
|
34
45
|
)
|
35
46
|
|