mirror of https://github.com/Qiskit/qiskit.git
Fixed ZZFeatureMap not accepting a list of entanglement (#12767)
* Added a get_entangler_map() and _selective_entangler_map() functions to PauliFeatureMap * Added some logic to handle the List[List[List[List[int]]]] case. Refer to the comments in the code. * `entanglement` can now be specified as dictionary `{num_qubits: entanglement}` * Added logic to handle cases when entanglement is not specified for some pauli blocks. * Added tests cases for `PauliFeatureMap` * Added test and releasenotes. Also modified the typing hints and doc strings to reflect new changes. * Replaced all tab characters with spaces to fix the linting error. * Formatted releasenotes as per the suggestion to fix the issue in reno. * Added `Callable[[int], Union[str | Dict[...]]]` to the typing hints and included some logic to handle that. * Removed `List[List[int]]` from typing hints as this is equivalent to List[Tuple[int]] Co-authored-by: Julien Gacon <gaconju@gmail.com> * updated the release notes Co-authored-by: Julien Gacon <gaconju@gmail.com> * Updated typing hints in `ZZFeatureMap` --------- Co-authored-by: Julien Gacon <gaconju@gmail.com>
This commit is contained in:
parent
3fac5da41e
commit
f30f851c98
|
@ -11,8 +11,7 @@
|
|||
# that they have been altered from the originals.
|
||||
|
||||
"""The Pauli expansion circuit module."""
|
||||
|
||||
from typing import Optional, Callable, List, Union
|
||||
from typing import Optional, Callable, List, Union, Sequence, Dict, Tuple
|
||||
from functools import reduce
|
||||
import numpy as np
|
||||
|
||||
|
@ -116,7 +115,11 @@ class PauliFeatureMap(NLocal):
|
|||
self,
|
||||
feature_dimension: Optional[int] = None,
|
||||
reps: int = 2,
|
||||
entanglement: Union[str, List[List[int]], Callable[[int], List[int]]] = "full",
|
||||
entanglement: Union[
|
||||
str,
|
||||
Dict[int, List[Tuple[int]]],
|
||||
Callable[[int], Union[str, Dict[int, List[Tuple[int]]]]],
|
||||
] = "full",
|
||||
alpha: float = 2.0,
|
||||
paulis: Optional[List[str]] = None,
|
||||
data_map_func: Optional[Callable[[np.ndarray], float]] = None,
|
||||
|
@ -129,8 +132,13 @@ class PauliFeatureMap(NLocal):
|
|||
Args:
|
||||
feature_dimension: Number of qubits in the circuit.
|
||||
reps: The number of repeated circuits.
|
||||
entanglement: Specifies the entanglement structure. Refer to
|
||||
:class:`~qiskit.circuit.library.NLocal` for detail.
|
||||
entanglement: Specifies the entanglement structure. Can be a string (``'full'``,
|
||||
``'linear'``, ``'reverse_linear'``, ``'circular'`` or ``'sca'``) or can be a
|
||||
dictionary where the keys represent the number of qubits and the values are list
|
||||
of integer-pairs specifying the indices of qubits that are entangled with one
|
||||
another, for example: ``{1: [(0,), (2,)], 2: [(0,1), (2,0)]}`` or can be a
|
||||
``Callable[[int], Union[str | Dict[...]]]`` to return an entanglement specific for
|
||||
a repetition
|
||||
alpha: The Pauli rotation factor, multiplicative to the pauli rotations
|
||||
paulis: A list of strings for to-be-used paulis. If None are provided, ``['Z', 'ZZ']``
|
||||
will be used.
|
||||
|
@ -281,6 +289,45 @@ class PauliFeatureMap(NLocal):
|
|||
basis_change(evo, inverse=True)
|
||||
return evo
|
||||
|
||||
def get_entangler_map(
|
||||
self, rep_num: int, block_num: int, num_block_qubits: int
|
||||
) -> Sequence[Sequence[int]]:
|
||||
|
||||
# if entanglement is a Callable[[int], Union[str | Dict[...]]]
|
||||
if callable(self._entanglement):
|
||||
entanglement = self._entanglement(rep_num)
|
||||
else:
|
||||
entanglement = self._entanglement
|
||||
|
||||
# entanglement is Dict[int, List[List[int]]]
|
||||
if isinstance(entanglement, dict):
|
||||
if all(
|
||||
isinstance(e2, (int, np.int32, np.int64))
|
||||
for key in entanglement.keys()
|
||||
for en in entanglement[key]
|
||||
for e2 in en
|
||||
):
|
||||
for qb, ent in entanglement.items():
|
||||
for en in ent:
|
||||
if len(en) != qb:
|
||||
raise ValueError(
|
||||
f"For num_qubits = {qb}, entanglement must be a "
|
||||
f"tuple of length {qb}. You specified {en}."
|
||||
)
|
||||
|
||||
# Check if the entanglement is specified for all the pauli blocks being used
|
||||
for pauli in self.paulis:
|
||||
if len(pauli) not in entanglement.keys():
|
||||
raise ValueError(f"No entanglement specified for {pauli} pauli.")
|
||||
|
||||
return entanglement[num_block_qubits]
|
||||
|
||||
else:
|
||||
# if the entanglement is not Dict[int, List[List[int]]] or
|
||||
# Dict[int, List[Tuple[int]]] then we fall back on the original
|
||||
# `get_entangler_map()` method from NLocal
|
||||
return super().get_entangler_map(rep_num, block_num, num_block_qubits)
|
||||
|
||||
|
||||
def self_product(x: np.ndarray) -> float:
|
||||
"""
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
"""Second-order Pauli-Z expansion circuit."""
|
||||
|
||||
from typing import Callable, List, Union, Optional
|
||||
from typing import Callable, List, Union, Optional, Dict, Tuple
|
||||
import numpy as np
|
||||
from .pauli_feature_map import PauliFeatureMap
|
||||
|
||||
|
@ -75,7 +75,11 @@ class ZZFeatureMap(PauliFeatureMap):
|
|||
self,
|
||||
feature_dimension: int,
|
||||
reps: int = 2,
|
||||
entanglement: Union[str, List[List[int]], Callable[[int], List[int]]] = "full",
|
||||
entanglement: Union[
|
||||
str,
|
||||
Dict[int, List[Tuple[int]]],
|
||||
Callable[[int], Union[str, Dict[int, List[Tuple[int]]]]],
|
||||
] = "full",
|
||||
data_map_func: Optional[Callable[[np.ndarray], float]] = None,
|
||||
parameter_prefix: str = "x",
|
||||
insert_barriers: bool = False,
|
||||
|
@ -87,7 +91,7 @@ class ZZFeatureMap(PauliFeatureMap):
|
|||
feature_dimension: Number of features.
|
||||
reps: The number of repeated circuits, has a min. value of 1.
|
||||
entanglement: Specifies the entanglement structure. Refer to
|
||||
:class:`~qiskit.circuit.library.NLocal` for detail.
|
||||
:class:`~qiskit.circuit.library.PauliFeatureMap` for detail.
|
||||
data_map_func: A mapping function for data x.
|
||||
parameter_prefix: The prefix used if default parameters are generated.
|
||||
insert_barriers: If True, barriers are inserted in between the evolution instructions
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
fixes:
|
||||
- |
|
||||
Fixed that the entanglement in :class:`.PauliFeatureMap` and :class:`.ZZFeatureMap`
|
||||
could be given as ``List[int]`` or ``List[List[int]]``, which was incompatible with the fact
|
||||
that entanglement blocks of different sizes are used. Instead, the entanglement can be
|
||||
given as dictionary with ``{block_size: entanglement}`` pairs.
|
||||
features_circuits:
|
||||
- |
|
||||
:class:`.PauliFeatureMap` and :class:`.ZZFeatureMap` now support specifying the
|
||||
entanglement as a dictionary where the keys represent the number of qubits, and
|
||||
the values are lists of integer tuples that define which qubits are entangled with one another. This
|
||||
allows for more flexibility in constructing feature maps tailored to specific quantum algorithms.
|
||||
Example usage::
|
||||
|
||||
from qiskit.circuit.library import PauliFeatureMap
|
||||
entanglement = {
|
||||
1: [(0,), (2,)],
|
||||
2: [(0, 1), (1, 2)],
|
||||
3: [(0, 1, 2)],
|
||||
}
|
||||
qc = PauliFeatureMap(3, reps=2, paulis=['Z', 'ZZ', 'ZZZ'], entanglement=entanglement, insert_barriers=True)
|
||||
qc.decompose().draw('mpl')
|
||||
|
||||
|
||||
|
|
@ -221,6 +221,79 @@ class TestDataPreparation(QiskitTestCase):
|
|||
self.assertEqual(str(encoding_z_param_y.parameters), "ParameterView([Parameter(y)])")
|
||||
self.assertEqual(str(encoding_zz_param_y.parameters), "ParameterView([Parameter(y)])")
|
||||
|
||||
def test_entanglement_as_dictionary(self):
|
||||
"""Test whether PauliFeatureMap accepts entanglement as a dictionary and generates
|
||||
correct feature map circuit"""
|
||||
n_qubits = 3
|
||||
entanglement = {
|
||||
1: [(0,), (2,)],
|
||||
2: [(0, 1), (1, 2)],
|
||||
3: [(0, 1, 2)],
|
||||
}
|
||||
params = [np.pi / 4, np.pi / 2, np.pi]
|
||||
|
||||
def z_block(circuit, q1):
|
||||
circuit.p(2 * params[q1], q1)
|
||||
|
||||
def zz_block(circuit, q1, q2):
|
||||
param = (np.pi - params[q1]) * (np.pi - params[q2])
|
||||
circuit.cx(q1, q2)
|
||||
circuit.p(2 * param, q2)
|
||||
circuit.cx(q1, q2)
|
||||
|
||||
def zzz_block(circuit, q1, q2, q3):
|
||||
param = (np.pi - params[q1]) * (np.pi - params[q2]) * (np.pi - params[q3])
|
||||
circuit.cx(q1, q2)
|
||||
circuit.cx(q2, q3)
|
||||
circuit.p(2 * param, q3)
|
||||
circuit.cx(q2, q3)
|
||||
circuit.cx(q1, q2)
|
||||
|
||||
feat_map = PauliFeatureMap(
|
||||
n_qubits, reps=2, paulis=["Z", "ZZ", "ZZZ"], entanglement=entanglement
|
||||
).assign_parameters(params)
|
||||
|
||||
qc = QuantumCircuit(n_qubits)
|
||||
for _ in range(2):
|
||||
qc.h([0, 1, 2])
|
||||
for e1 in entanglement[1]:
|
||||
z_block(qc, *e1)
|
||||
for e2 in entanglement[2]:
|
||||
zz_block(qc, *e2)
|
||||
for e3 in entanglement[3]:
|
||||
zzz_block(qc, *e3)
|
||||
|
||||
self.assertTrue(Operator(feat_map).equiv(qc))
|
||||
|
||||
def test_invalid_entanglement(self):
|
||||
"""Test if a ValueError is raised when an invalid entanglement is passed"""
|
||||
n_qubits = 3
|
||||
entanglement = {
|
||||
1: [(0, 1), (2,)],
|
||||
2: [(0, 1), (1, 2)],
|
||||
3: [(0, 1, 2)],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
feat_map = PauliFeatureMap(
|
||||
n_qubits, reps=2, paulis=["Z", "ZZ", "ZZZ"], entanglement=entanglement
|
||||
)
|
||||
feat_map.count_ops()
|
||||
|
||||
def test_entanglement_not_specified(self):
|
||||
"""Test if an error is raised when entanglement is not explicitly specified for
|
||||
all n-qubit pauli blocks"""
|
||||
n_qubits = 3
|
||||
entanglement = {
|
||||
1: [(0, 1), (2,)],
|
||||
3: [(0, 1, 2)],
|
||||
}
|
||||
with self.assertRaises(ValueError):
|
||||
feat_map = PauliFeatureMap(
|
||||
n_qubits, reps=2, paulis=["Z", "ZZ", "ZZZ"], entanglement=entanglement
|
||||
)
|
||||
feat_map.count_ops()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in New Issue