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.
- classiq/_internals/api_wrapper.py +27 -0
- classiq/applications/chemistry/chemistry_model_constructor.py +0 -2
- 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_problem.py +20 -42
- classiq/{model_expansions/evaluators → evaluators}/arg_type_match.py +12 -4
- classiq/{model_expansions/evaluators → evaluators}/argument_types.py +1 -1
- classiq/evaluators/classical_expression.py +53 -0
- 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 +238 -49
- 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/debug_info/debug_info.py +0 -4
- 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/expressions/atomic_expression_functions.py +10 -1
- classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +0 -6
- classiq/interface/generator/functions/builtins/internal_operators.py +2 -0
- classiq/interface/generator/functions/classical_type.py +2 -35
- classiq/interface/generator/functions/concrete_types.py +20 -3
- classiq/interface/generator/functions/type_modifier.py +0 -19
- classiq/interface/generator/generated_circuit_data.py +5 -18
- classiq/interface/generator/types/compilation_metadata.py +0 -3
- classiq/interface/ide/operation_registry.py +45 -0
- classiq/interface/ide/visual_model.py +68 -3
- classiq/interface/model/bounds.py +12 -2
- classiq/interface/model/model.py +12 -7
- classiq/interface/model/port_declaration.py +2 -24
- classiq/interface/model/quantum_expressions/arithmetic_operation.py +7 -4
- classiq/interface/model/variable_declaration_statement.py +33 -6
- 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 +47 -6
- classiq/model_expansions/function_builder.py +4 -1
- classiq/model_expansions/interpreters/base_interpreter.py +3 -3
- classiq/model_expansions/interpreters/generative_interpreter.py +16 -1
- classiq/model_expansions/quantum_operations/allocate.py +1 -1
- classiq/model_expansions/quantum_operations/assignment_result_processor.py +64 -22
- classiq/model_expansions/quantum_operations/bind.py +2 -2
- classiq/model_expansions/quantum_operations/bounds.py +7 -1
- classiq/model_expansions/quantum_operations/call_emitter.py +26 -20
- classiq/model_expansions/quantum_operations/classical_var_emitter.py +16 -0
- classiq/model_expansions/quantum_operations/variable_decleration.py +31 -11
- classiq/model_expansions/scope.py +7 -0
- classiq/model_expansions/scope_initialization.py +3 -3
- classiq/model_expansions/transformers/model_renamer.py +6 -4
- classiq/model_expansions/transformers/type_modifier_inference.py +81 -43
- classiq/model_expansions/transformers/var_splitter.py +1 -1
- classiq/model_expansions/visitors/symbolic_param_inference.py +2 -3
- classiq/open_library/functions/__init__.py +3 -2
- 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/qaoa_penalty.py +8 -1
- classiq/open_library/functions/state_preparation.py +18 -32
- classiq/qmod/__init__.py +2 -0
- classiq/qmod/builtins/enums.py +23 -0
- classiq/qmod/builtins/functions/__init__.py +2 -0
- classiq/qmod/builtins/functions/exponentiation.py +32 -4
- classiq/qmod/builtins/operations.py +65 -1
- classiq/qmod/builtins/structs.py +55 -3
- classiq/qmod/classical_variable.py +74 -0
- classiq/qmod/declaration_inferrer.py +3 -2
- classiq/qmod/native/pretty_printer.py +20 -20
- classiq/qmod/pretty_print/expression_to_python.py +2 -1
- classiq/qmod/pretty_print/pretty_printer.py +35 -21
- classiq/qmod/python_classical_type.py +12 -5
- classiq/qmod/qfunc.py +2 -19
- classiq/qmod/qmod_constant.py +2 -5
- classiq/qmod/qmod_parameter.py +2 -5
- classiq/qmod/qmod_variable.py +61 -23
- classiq/qmod/quantum_expandable.py +5 -3
- classiq/qmod/quantum_function.py +49 -4
- classiq/qmod/semantics/annotation/qstruct_annotator.py +1 -1
- classiq/qmod/semantics/validation/main_validation.py +1 -9
- classiq/qmod/symbolic_type.py +2 -1
- classiq/qmod/utilities.py +0 -2
- classiq/qmod/write_qmod.py +1 -1
- {classiq-0.83.0.dist-info → classiq-0.85.0.dist-info}/METADATA +4 -1
- {classiq-0.83.0.dist-info → classiq-0.85.0.dist-info}/RECORD +101 -90
- classiq/interface/model/quantum_variable_declaration.py +0 -7
- classiq/model_expansions/evaluators/classical_expression.py +0 -36
- /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.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
|
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.
|
48
|
-
self.
|
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.
|
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
|
-
|
104
|
-
self.qprog_, execution_preferences # type:ignore[
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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.
|
150
|
-
res = self.
|
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,
|
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
|
@@ -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.
|
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,
|
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=(
|