Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed ZZFeatureMap not accepting a list of entanglement #12767

Merged
merged 12 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 52 additions & 5 deletions qiskit/circuit/library/data_preparation/pauli_feature_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -116,7 +115,11 @@ def __init__(
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,
Expand All @@ -129,8 +132,13 @@ def __init__(
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.
Expand Down Expand Up @@ -281,6 +289,45 @@ def cx_chain(circuit, inverse=False):
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:
"""
Expand Down
10 changes: 7 additions & 3 deletions qiskit/circuit/library/data_preparation/zz_feature_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -75,7 +75,11 @@ def __init__(
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,
Expand All @@ -87,7 +91,7 @@ def __init__(
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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')



73 changes: 73 additions & 0 deletions test/python/circuit/library/test_pauli_feature_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,79 @@ def test_parameter_prefix(self):
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