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

Allow to normalize inputs on StatePreparation and Initialize #7189

Merged
merged 22 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c3c92b5
added normalize func
daniel-fry Oct 28, 2021
14614b7
remove typecheck
daniel-fry Oct 28, 2021
1c3fcf5
changed to handle numpy arrays and lists both with complex numbers
daniel-fry Oct 29, 2021
6d17d58
Merge branch 'main' into qiskit_issue1
daniel-fry Oct 29, 2021
e12d1ab
Corrected docstring for normalization function
daniel-fry Nov 1, 2021
548376a
Merge branch 'qiskit_issue1' of https://github.com/daniel-fry/qiskit-…
daniel-fry Nov 1, 2021
6628c54
Merge branch 'Qiskit:main' into qiskit_issue1
daniel-fry Jan 28, 2022
eeada47
Add files via upload
daniel-fry Jan 28, 2022
337e3e4
Merge branch 'Qiskit:main' into qiskit_issue1
daniel-fry Jan 31, 2022
4b60a45
initializer.py normalize function
daniel-fry Jan 31, 2022
26ba4f7
fix line endings
daniel-fry Jan 31, 2022
3825d12
Merge branch 'Qiskit:main' into qiskit_issue1
daniel-fry Feb 1, 2022
7fe2376
addressing edits in docstrings and normalize function
daniel-fry Feb 1, 2022
7996909
Merge branch 'Qiskit:main' into qiskit_issue1
daniel-fry Feb 7, 2022
c86f865
review comments
Cryoris Mar 21, 2022
c5abcc8
Merge branch 'main' into qiskit_issue1
Cryoris Mar 21, 2022
b30921e
attempt 1 to fix sphinx
Cryoris Mar 21, 2022
e701708
Merge branch 'main' into qiskit_issue1
Cryoris May 8, 2023
ce122d1
add reno and improve error message
Cryoris May 9, 2023
9bbceb3
fix passing normalize to stateprep
Cryoris May 9, 2023
6c991fc
Merge remote-tracking branch 'ibm/main' into qiskit_issue1
jakelishman Jun 20, 2023
3fd5e6e
Fixup PR
jakelishman Jun 20, 2023
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
22 changes: 15 additions & 7 deletions qiskit/circuit/library/data_preparation/state_preparation.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(
num_qubits: Optional[int] = None,
inverse: bool = False,
label: Optional[str] = None,
normalize: bool = False,
):
r"""
Args:
Expand All @@ -63,6 +64,7 @@ def __init__(
and the remaining 3 qubits to be initialized to :math:`|0\rangle`.
inverse: if True, the inverse state is constructed.
label: An optional label for the gate
normalize (bool): Whether to normalize an input array to a unit vector.

Raises:
QiskitError: ``num_qubits`` parameter used when ``params`` is not an integer
Expand Down Expand Up @@ -96,8 +98,15 @@ def __init__(
self._from_label = isinstance(params, str)
self._from_int = isinstance(params, int)

num_qubits = self._get_num_qubits(num_qubits, params)
# if initialized from a vector, check that the parameters are normalized
if not self._from_label and not self._from_int:
norm = np.linalg.norm(params)
if normalize:
params = np.array(params, dtype=np.complex128) / norm
elif not math.isclose(norm, 1.0, abs_tol=_EPS):
raise QiskitError(f"Sum of amplitudes-squared is not 1, but {norm}.")

num_qubits = self._get_num_qubits(num_qubits, params)
params = [params] if isinstance(params, int) else params

super().__init__(self._name, num_qubits, params, label=self._label)
Expand Down Expand Up @@ -197,10 +206,6 @@ def _get_num_qubits(self, num_qubits, params):
if num_qubits == 0 or not num_qubits.is_integer():
raise QiskitError("Desired statevector length not a positive power of 2.")

# Check if probabilities (amplitudes squared) sum to 1
if not math.isclose(sum(np.absolute(params) ** 2), 1.0, abs_tol=_EPS):
raise QiskitError("Sum of amplitudes-squared does not equal one.")

num_qubits = int(num_qubits)
return num_qubits

Expand Down Expand Up @@ -409,7 +414,7 @@ def _multiplex(self, target_gate, list_of_angles, last_cnot=True):
return circuit


def prepare_state(self, state, qubits=None, label=None):
def prepare_state(self, state, qubits=None, label=None, normalize=False):
r"""Prepare qubits in a specific state.

This class implements a state preparing unitary. Unlike
Expand All @@ -433,6 +438,7 @@ def prepare_state(self, state, qubits=None, label=None):
* int: Index of qubit to be initialized [Default: None].
* list: Indexes of qubits to be initialized [Default: None].
label (str): An optional label for the gate
normalize (bool): Whether to normalize an input array to a unit vector.

Returns:
qiskit.circuit.Instruction: a handle to the instruction that was just initialized
Expand Down Expand Up @@ -511,7 +517,9 @@ def prepare_state(self, state, qubits=None, label=None):

num_qubits = len(qubits) if isinstance(state, int) else None

return self.append(StatePreparation(state, num_qubits, label=label), qubits)
return self.append(
StatePreparation(state, num_qubits, label=label, normalize=normalize), qubits
)


QuantumCircuit.prepare_state = prepare_state
11 changes: 7 additions & 4 deletions qiskit/extensions/quantum_initializer/initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Initialize(Instruction):
which is not unitary.
"""

def __init__(self, params, num_qubits=None):
def __init__(self, params, num_qubits=None, normalize=False):
r"""Create new initialize composite.

Args:
Expand All @@ -53,8 +53,9 @@ def __init__(self, params, num_qubits=None):
number of qubits in the `initialize` call. Example: `initialize` covers 5 qubits
and params is 3. This allows qubits 0 and 1 to be initialized to :math:`|1\rangle`
and the remaining 3 qubits to be initialized to :math:`|0\rangle`.
normalize (bool): Whether to normalize an input array to a unit vector.
"""
self._stateprep = StatePreparation(params, num_qubits)
self._stateprep = StatePreparation(params, num_qubits, normalize=normalize)

super().__init__("initialize", self._stateprep.num_qubits, 0, self._stateprep.params)

Expand Down Expand Up @@ -87,7 +88,7 @@ def broadcast_arguments(self, qargs, cargs):
return self._stateprep.broadcast_arguments(qargs, cargs)


def initialize(self, params, qubits=None):
def initialize(self, params, qubits=None, normalize=False):
r"""Initialize qubits in a specific state.

Qubit initialization is done by first resetting the qubits to :math:`|0\rangle`
Expand All @@ -113,6 +114,8 @@ class to prepare the qubits in a specified state.
* int: Index of qubit to be initialized [Default: None].
* list: Indexes of qubits to be initialized [Default: None].

normalize (bool): whether to normalize an input array to a unit vector.

Returns:
qiskit.circuit.Instruction: a handle to the instruction that was just initialized

Expand Down Expand Up @@ -188,7 +191,7 @@ class to prepare the qubits in a specified state.
qubits = [qubits]
num_qubits = len(qubits) if isinstance(params, int) else None

return self.append(Initialize(params, num_qubits), qubits)
return self.append(Initialize(params, num_qubits, normalize), qubits)


QuantumCircuit.initialize = initialize
8 changes: 8 additions & 0 deletions releasenotes/notes/normalize-stateprep-e21972dce8695509.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
features:
- |
The instructions :class:`.StatePreparation` and :class:`~.extensions.Initialize`,
and their associated circuit methods :meth:`.QuantumCircuit.prepare_state` and :meth:`~.QuantumCircuit.initialize`,
gained a keyword argument ``normalize``, which can be set to ``True`` to automatically normalize
an array target. By default this is ``False``, which retains the current behaviour of
raising an exception when given non-normalized input.
11 changes: 11 additions & 0 deletions test/python/circuit/test_initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,17 @@ def test_non_unit_probability(self):
qc = QuantumCircuit(qr)
self.assertRaises(QiskitError, qc.initialize, desired_vector, [qr[0], qr[1]])

def test_normalize(self):
"""Test initializing with a non-normalized vector is normalized, if specified."""
desired_vector = [1, 1]
normalized = np.asarray(desired_vector) / np.linalg.norm(desired_vector)

qc = QuantumCircuit(1)
qc.initialize(desired_vector, [0], normalize=True)
op = qc.data[0].operation
self.assertAlmostEqual(np.linalg.norm(op.params), 1)
self.assertEqual(Statevector(qc), Statevector(normalized))

def test_wrong_vector_size(self):
"""Initializing to a vector with a size different to the qubit parameter length.
See https://github.com/Qiskit/qiskit-terra/issues/2372"""
Expand Down