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.
|
# that they have been altered from the originals.
|
||||||
|
|
||||||
"""The Pauli expansion circuit module."""
|
"""The Pauli expansion circuit module."""
|
||||||
|
from typing import Optional, Callable, List, Union, Sequence, Dict, Tuple
|
||||||
from typing import Optional, Callable, List, Union
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
@ -116,7 +115,11 @@ class PauliFeatureMap(NLocal):
|
||||||
self,
|
self,
|
||||||
feature_dimension: Optional[int] = None,
|
feature_dimension: Optional[int] = None,
|
||||||
reps: int = 2,
|
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,
|
alpha: float = 2.0,
|
||||||
paulis: Optional[List[str]] = None,
|
paulis: Optional[List[str]] = None,
|
||||||
data_map_func: Optional[Callable[[np.ndarray], float]] = None,
|
data_map_func: Optional[Callable[[np.ndarray], float]] = None,
|
||||||
|
@ -129,8 +132,13 @@ class PauliFeatureMap(NLocal):
|
||||||
Args:
|
Args:
|
||||||
feature_dimension: Number of qubits in the circuit.
|
feature_dimension: Number of qubits in the circuit.
|
||||||
reps: The number of repeated circuits.
|
reps: The number of repeated circuits.
|
||||||
entanglement: Specifies the entanglement structure. Refer to
|
entanglement: Specifies the entanglement structure. Can be a string (``'full'``,
|
||||||
:class:`~qiskit.circuit.library.NLocal` for detail.
|
``'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
|
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']``
|
paulis: A list of strings for to-be-used paulis. If None are provided, ``['Z', 'ZZ']``
|
||||||
will be used.
|
will be used.
|
||||||
|
@ -281,6 +289,45 @@ class PauliFeatureMap(NLocal):
|
||||||
basis_change(evo, inverse=True)
|
basis_change(evo, inverse=True)
|
||||||
return evo
|
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:
|
def self_product(x: np.ndarray) -> float:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
"""Second-order Pauli-Z expansion circuit."""
|
"""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
|
import numpy as np
|
||||||
from .pauli_feature_map import PauliFeatureMap
|
from .pauli_feature_map import PauliFeatureMap
|
||||||
|
|
||||||
|
@ -75,7 +75,11 @@ class ZZFeatureMap(PauliFeatureMap):
|
||||||
self,
|
self,
|
||||||
feature_dimension: int,
|
feature_dimension: int,
|
||||||
reps: int = 2,
|
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,
|
data_map_func: Optional[Callable[[np.ndarray], float]] = None,
|
||||||
parameter_prefix: str = "x",
|
parameter_prefix: str = "x",
|
||||||
insert_barriers: bool = False,
|
insert_barriers: bool = False,
|
||||||
|
@ -87,7 +91,7 @@ class ZZFeatureMap(PauliFeatureMap):
|
||||||
feature_dimension: Number of features.
|
feature_dimension: Number of features.
|
||||||
reps: The number of repeated circuits, has a min. value of 1.
|
reps: The number of repeated circuits, has a min. value of 1.
|
||||||
entanglement: Specifies the entanglement structure. Refer to
|
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.
|
data_map_func: A mapping function for data x.
|
||||||
parameter_prefix: The prefix used if default parameters are generated.
|
parameter_prefix: The prefix used if default parameters are generated.
|
||||||
insert_barriers: If True, barriers are inserted in between the evolution instructions
|
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_z_param_y.parameters), "ParameterView([Parameter(y)])")
|
||||||
self.assertEqual(str(encoding_zz_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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in New Issue