# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
Statevector quantum state class.
"""
from __future__ import annotations
import copy
import re
from numbers import Number
import numpy as np
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.instruction import Instruction
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.states.quantum_state import QuantumState
from qiskit.quantum_info.operators.mixins.tolerances import TolerancesMixin
from qiskit.quantum_info.operators.operator import Operator, BaseOperator
from qiskit.quantum_info.operators.symplectic import Pauli, SparsePauliOp
from qiskit.quantum_info.operators.op_shape import OpShape
from qiskit.quantum_info.operators.predicates import matrix_equal
from qiskit._accelerate.pauli_expval import (
expval_pauli_no_x,
expval_pauli_with_x,
)
[documentos]class Statevector(QuantumState, TolerancesMixin):
"""Statevector class"""
def __init__(
self,
data: np.ndarray | list | Statevector | Operator | QuantumCircuit | Instruction,
dims: int | tuple | list | None = None,
):
"""Initialize a statevector object.
Args:
data (np.array or list or Statevector or Operator or QuantumCircuit or
qiskit.circuit.Instruction):
Data from which the statevector can be constructed. This can be either a complex
vector, another statevector, a ``Operator`` with only one column or a
``QuantumCircuit`` or ``Instruction``. If the data is a circuit or instruction,
the statevector is constructed by assuming that all qubits are initialized to the
zero state.
dims (int or tuple or list): Optional. The subsystem dimension of
the state (See additional information).
Raises:
QiskitError: if input data is not valid.
Additional Information:
The ``dims`` kwarg can be None, an integer, or an iterable of
integers.
* ``Iterable`` -- the subsystem dimensions are the values in the list
with the total number of subsystems given by the length of the list.
* ``Int`` or ``None`` -- the length of the input vector
specifies the total dimension of the density matrix. If it is a
power of two the state will be initialized as an N-qubit state.
If it is not a power of two the state will have a single
d-dimensional subsystem.
"""
if isinstance(data, (list, np.ndarray)):
# Finally we check if the input is a raw vector in either a
# python list or numpy array format.
self._data = np.asarray(data, dtype=complex)
elif isinstance(data, Statevector):
self._data = data._data
if dims is None:
dims = data._op_shape._dims_l
elif isinstance(data, Operator):
# We allow conversion of column-vector operators to Statevectors
input_dim, _ = data.dim
if input_dim != 1:
raise QiskitError("Input Operator is not a column-vector.")
self._data = np.ravel(data.data)
elif isinstance(data, (QuantumCircuit, Instruction)):
self._data = Statevector.from_instruction(data).data
else:
raise QiskitError("Invalid input data format for Statevector")
# Check that the input is a numpy vector or column-vector numpy
# matrix. If it is a column-vector matrix reshape to a vector.
ndim = self._data.ndim
shape = self._data.shape
if ndim != 1:
if ndim == 2 and shape[1] == 1:
self._data = np.reshape(self._data, shape[0])
shape = self._data.shape
elif ndim != 2 or shape[1] != 1:
raise QiskitError("Invalid input: not a vector or column-vector.")
super().__init__(op_shape=OpShape.auto(shape=shape, dims_l=dims, num_qubits_r=0))
def __array__(self, dtype=None):
if dtype:
return np.asarray(self.data, dtype=dtype)
return self.data
def __eq__(self, other):
return super().__eq__(other) and np.allclose(
self._data, other._data, rtol=self.rtol, atol=self.atol
)
def __repr__(self):
prefix = "Statevector("
pad = len(prefix) * " "
return "{}{},\n{}dims={})".format(
prefix,
np.array2string(self._data, separator=", ", prefix=prefix),
pad,
self._op_shape.dims_l(),
)
@property
def settings(self) -> dict:
"""Return settings."""
return {"data": self._data, "dims": self._op_shape.dims_l()}
[documentos] def draw(self, output: str | None = None, **drawer_args):
"""Return a visualization of the Statevector.
**repr**: ASCII TextMatrix of the state's ``__repr__``.
**text**: ASCII TextMatrix that can be printed in the console.
**latex**: An IPython Latex object for displaying in Jupyter Notebooks.
**latex_source**: Raw, uncompiled ASCII source to generate array using LaTeX.
**qsphere**: Matplotlib figure, rendering of statevector using `plot_state_qsphere()`.
**hinton**: Matplotlib figure, rendering of statevector using `plot_state_hinton()`.
**bloch**: Matplotlib figure, rendering of statevector using `plot_bloch_multivector()`.
**city**: Matplotlib figure, rendering of statevector using `plot_state_city()`.
**paulivec**: Matplotlib figure, rendering of statevector using `plot_state_paulivec()`.
Args:
output (str): Select the output method to use for drawing the
state. Valid choices are `repr`, `text`, `latex`, `latex_source`,
`qsphere`, `hinton`, `bloch`, `city`, or `paulivec`. Default is `repr`.
Default can be changed by adding the line ``state_drawer = <default>`` to
``~/.qiskit/settings.conf`` under ``[default]``.
drawer_args: Arguments to be passed directly to the relevant drawing
function or constructor (`TextMatrix()`, `array_to_latex()`,
`plot_state_qsphere()`, `plot_state_hinton()` or `plot_bloch_multivector()`).
See the relevant function under `qiskit.visualization` for that function's
documentation.
Returns:
:class:`matplotlib.Figure` or :class:`str` or
:class:`TextMatrix` or :class:`IPython.display.Latex`:
Drawing of the Statevector.
Raises:
ValueError: when an invalid output method is selected.
Examples:
Plot one of the Bell states
.. plot::
:include-source:
from numpy import sqrt
from qiskit.quantum_info import Statevector
sv=Statevector([1/sqrt(2), 0, 0, -1/sqrt(2)])
sv.draw(output='hinton')
"""
# pylint: disable=cyclic-import
from qiskit.visualization.state_visualization import state_drawer
return state_drawer(self, output=output, **drawer_args)
def _ipython_display_(self):
out = self.draw()
if isinstance(out, str):
print(out)
else:
from IPython.display import display
display(out)
def __getitem__(self, key: int | str) -> np.complex128:
"""Return Statevector item either by index or binary label
Args:
key (int or str): index or corresponding binary label, e.g. '01' = 1.
Returns:
numpy.complex128: Statevector item.
Raises:
QiskitError: if key is not valid.
"""
if isinstance(key, str):
try:
key = int(key, 2)
except ValueError:
raise QiskitError(f"Key '{key}' is not a valid binary string.") from None
if isinstance(key, int):
if key >= self.dim:
raise QiskitError(f"Key {key} is greater than Statevector dimension {self.dim}.")
if key < 0:
raise QiskitError(f"Key {key} is not a valid positive value.")
return self._data[key]
else:
raise QiskitError("Key must be int or a valid binary string.")
def __iter__(self):
yield from self._data
def __len__(self):
return len(self._data)
@property
def data(self) -> np.ndarray:
"""Return data."""
return self._data
[documentos] def is_valid(self, atol: float | None = None, rtol: float | None = None) -> bool:
"""Return True if a Statevector has norm 1."""
if atol is None:
atol = self.atol
if rtol is None:
rtol = self.rtol
norm = np.linalg.norm(self.data)
return np.allclose(norm, 1, rtol=rtol, atol=atol)
[documentos] def to_operator(self) -> Operator:
"""Convert state to a rank-1 projector operator"""
mat = np.outer(self.data, np.conj(self.data))
return Operator(mat, input_dims=self.dims(), output_dims=self.dims())
[documentos] def conjugate(self) -> Statevector:
"""Return the conjugate of the operator."""
return Statevector(np.conj(self.data), dims=self.dims())
[documentos] def trace(self) -> np.float64:
"""Return the trace of the quantum state as a density matrix."""
return np.sum(np.abs(self.data) ** 2)
[documentos] def purity(self) -> np.float64:
"""Return the purity of the quantum state."""
# For a valid statevector the purity is always 1, however if we simply
# have an arbitrary vector (not correctly normalized) then the
# purity is equivalent to the trace squared:
# P(|psi>) = Tr[|psi><psi|psi><psi|] = |<psi|psi>|^2
return self.trace() ** 2
[documentos] def tensor(self, other: Statevector) -> Statevector:
"""Return the tensor product state self ⊗ other.
Args:
other (Statevector): a quantum state object.
Returns:
Statevector: the tensor product operator self ⊗ other.
Raises:
QiskitError: if other is not a quantum state.
"""
if not isinstance(other, Statevector):
other = Statevector(other)
ret = copy.copy(self)
ret._op_shape = self._op_shape.tensor(other._op_shape)
ret._data = np.kron(self._data, other._data)
return ret
[documentos] def inner(self, other: Statevector) -> np.complex128:
r"""Return the inner product of self and other as
:math:`\langle self| other \rangle`.
Args:
other (Statevector): a quantum state object.
Returns:
np.complex128: the inner product of self and other, :math:`\langle self| other \rangle`.
Raises:
QiskitError: if other is not a quantum state or has different dimension.
"""
if not isinstance(other, Statevector):
other = Statevector(other)
if self.dims() != other.dims():
raise QiskitError(
f"Statevector dimensions do not match: {self.dims()} and {other.dims()}."
)
inner = np.vdot(self.data, other.data)
return inner
[documentos] def expand(self, other: Statevector) -> Statevector:
"""Return the tensor product state other ⊗ self.
Args:
other (Statevector): a quantum state object.
Returns:
Statevector: the tensor product state other ⊗ self.
Raises:
QiskitError: if other is not a quantum state.
"""
if not isinstance(other, Statevector):
other = Statevector(other)
ret = copy.copy(self)
ret._op_shape = self._op_shape.expand(other._op_shape)
ret._data = np.kron(other._data, self._data)
return ret
def _add(self, other):
"""Return the linear combination self + other.
Args:
other (Statevector): a quantum state object.
Returns:
Statevector: the linear combination self + other.
Raises:
QiskitError: if other is not a quantum state, or has
incompatible dimensions.
"""
if not isinstance(other, Statevector):
other = Statevector(other)
self._op_shape._validate_add(other._op_shape)
ret = copy.copy(self)
ret._data = self.data + other.data
return ret
def _multiply(self, other):
"""Return the scalar multiplied state self * other.
Args:
other (complex): a complex number.
Returns:
Statevector: the scalar multiplied state other * self.
Raises:
QiskitError: if other is not a valid complex number.
"""
if not isinstance(other, Number):
raise QiskitError("other is not a number")
ret = copy.copy(self)
ret._data = other * self.data
return ret
[documentos] def evolve(
self, other: Operator | QuantumCircuit | Instruction, qargs: list[int] | None = None
) -> Statevector:
"""Evolve a quantum state by the operator.
Args:
other (Operator | QuantumCircuit | circuit.Instruction): The operator to evolve by.
qargs (list): a list of Statevector subsystem positions to apply
the operator on.
Returns:
Statevector: the output quantum state.
Raises:
QiskitError: if the operator dimension does not match the
specified Statevector subsystem dimensions.
"""
if qargs is None:
qargs = getattr(other, "qargs", None)
# Get return vector
ret = copy.copy(self)
# Evolution by a circuit or instruction
if isinstance(other, QuantumCircuit):
other = other.to_instruction()
if isinstance(other, Instruction):
if self.num_qubits is None:
raise QiskitError("Cannot apply QuantumCircuit to non-qubit Statevector.")
return self._evolve_instruction(ret, other, qargs=qargs)
# Evolution by an Operator
if not isinstance(other, Operator):
dims = self.dims(qargs=qargs)
other = Operator(other, input_dims=dims, output_dims=dims)
# check dimension
if self.dims(qargs) != other.input_dims():
raise QiskitError(
"Operator input dimensions are not equal to statevector subsystem dimensions."
)
return Statevector._evolve_operator(ret, other, qargs=qargs)
[documentos] def equiv(
self, other: Statevector, rtol: float | None = None, atol: float | None = None
) -> bool:
"""Return True if other is equivalent as a statevector up to global phase.
.. note::
If other is not a Statevector, but can be used to initialize a statevector object,
this will check that Statevector(other) is equivalent to the current statevector up
to global phase.
Args:
other (Statevector): an object from which a ``Statevector`` can be constructed.
rtol (float): relative tolerance value for comparison.
atol (float): absolute tolerance value for comparison.
Returns:
bool: True if statevectors are equivalent up to global phase.
"""
if not isinstance(other, Statevector):
try:
other = Statevector(other)
except QiskitError:
return False
if self.dim != other.dim:
return False
if atol is None:
atol = self.atol
if rtol is None:
rtol = self.rtol
return matrix_equal(self.data, other.data, ignore_phase=True, rtol=rtol, atol=atol)
[documentos] def reverse_qargs(self) -> Statevector:
r"""Return a Statevector with reversed subsystem ordering.
For a tensor product state this is equivalent to reversing the order
of tensor product subsystems. For a statevector
:math:`|\psi \rangle = |\psi_{n-1} \rangle \otimes ... \otimes |\psi_0 \rangle`
the returned statevector will be
:math:`|\psi_{0} \rangle \otimes ... \otimes |\psi_{n-1} \rangle`.
Returns:
Statevector: the Statevector with reversed subsystem order.
"""
ret = copy.copy(self)
axes = tuple(range(self._op_shape._num_qargs_l - 1, -1, -1))
ret._data = np.reshape(
np.transpose(np.reshape(self.data, self._op_shape.tensor_shape), axes),
self._op_shape.shape,
)
ret._op_shape = self._op_shape.reverse()
return ret
def _expectation_value_pauli(self, pauli, qargs=None):
"""Compute the expectation value of a Pauli.
Args:
pauli (Pauli): a Pauli operator to evaluate expval of.
qargs (None or list): subsystems to apply operator on.
Returns:
complex: the expectation value.
"""
n_pauli = len(pauli)
if qargs is None:
qubits = np.arange(n_pauli)
else:
qubits = np.array(qargs)
x_mask = np.dot(1 << qubits, pauli.x)
z_mask = np.dot(1 << qubits, pauli.z)
pauli_phase = (-1j) ** pauli.phase if pauli.phase else 1
if x_mask + z_mask == 0:
return pauli_phase * np.linalg.norm(self.data)
if x_mask == 0:
return pauli_phase * expval_pauli_no_x(self.data, self.num_qubits, z_mask)
x_max = qubits[pauli.x][-1]
y_phase = (-1j) ** pauli._count_y()
y_phase = y_phase[0]
return pauli_phase * expval_pauli_with_x(
self.data, self.num_qubits, z_mask, x_mask, y_phase, x_max
)
[documentos] def expectation_value(
self, oper: BaseOperator | QuantumCircuit | Instruction, qargs: None | list[int] = None
) -> complex:
"""Compute the expectation value of an operator.
Args:
oper (Operator): an operator to evaluate expval of.
qargs (None or list): subsystems to apply operator on.
Returns:
complex: the expectation value.
"""
if isinstance(oper, Pauli):
return self._expectation_value_pauli(oper, qargs)
if isinstance(oper, SparsePauliOp):
return sum(
coeff * self._expectation_value_pauli(Pauli((z, x)), qargs)
for z, x, coeff in zip(oper.paulis.z, oper.paulis.x, oper.coeffs)
)
val = self.evolve(oper, qargs=qargs)
conj = self.conjugate()
return np.dot(conj.data, val.data)
[documentos] def probabilities(
self, qargs: None | list[int] = None, decimals: None | int = None
) -> np.ndarray:
"""Return the subsystem measurement probability vector.
Measurement probabilities are with respect to measurement in the
computation (diagonal) basis.
Args:
qargs (None or list): subsystems to return probabilities for,
if None return for all subsystems (Default: None).
decimals (None or int): the number of decimal places to round
values. If None no rounding is done (Default: None).
Returns:
np.array: The Numpy vector array of probabilities.
Examples:
Consider a 2-qubit product state
:math:`|\\psi\\rangle=|+\\rangle\\otimes|0\\rangle`.
.. code-block::
from qiskit.quantum_info import Statevector
psi = Statevector.from_label('+0')
# Probabilities for measuring both qubits
probs = psi.probabilities()
print('probs: {}'.format(probs))
# Probabilities for measuring only qubit-0
probs_qubit_0 = psi.probabilities([0])
print('Qubit-0 probs: {}'.format(probs_qubit_0))
# Probabilities for measuring only qubit-1
probs_qubit_1 = psi.probabilities([1])
print('Qubit-1 probs: {}'.format(probs_qubit_1))
.. parsed-literal::
probs: [0.5 0. 0.5 0. ]
Qubit-0 probs: [1. 0.]
Qubit-1 probs: [0.5 0.5]
We can also permute the order of qubits in the ``qargs`` list
to change the qubit position in the probabilities output
.. code-block::
from qiskit.quantum_info import Statevector
psi = Statevector.from_label('+0')
# Probabilities for measuring both qubits
probs = psi.probabilities([0, 1])
print('probs: {}'.format(probs))
# Probabilities for measuring both qubits
# but swapping qubits 0 and 1 in output
probs_swapped = psi.probabilities([1, 0])
print('Swapped probs: {}'.format(probs_swapped))
.. parsed-literal::
probs: [0.5 0. 0.5 0. ]
Swapped probs: [0.5 0.5 0. 0. ]
"""
probs = self._subsystem_probabilities(
np.abs(self.data) ** 2, self._op_shape.dims_l(), qargs=qargs
)
# to account for roundoff errors, we clip
probs = np.clip(probs, a_min=0, a_max=1)
if decimals is not None:
probs = probs.round(decimals=decimals)
return probs
[documentos] def reset(self, qargs: list[int] | None = None) -> Statevector:
"""Reset state or subsystems to the 0-state.
Args:
qargs (list or None): subsystems to reset, if None all
subsystems will be reset to their 0-state
(Default: None).
Returns:
Statevector: the reset state.
Additional Information:
If all subsystems are reset this will return the ground state
on all subsystems. If only a some subsystems are reset this
function will perform a measurement on those subsystems and
evolve the subsystems so that the collapsed post-measurement
states are rotated to the 0-state. The RNG seed for this
sampling can be set using the :meth:`seed` method.
"""
if qargs is None:
# Resetting all qubits does not require sampling or RNG
ret = copy.copy(self)
state = np.zeros(self._op_shape.shape, dtype=complex)
state[0] = 1
ret._data = state
return ret
# Sample a single measurement outcome
dims = self.dims(qargs)
probs = self.probabilities(qargs)
sample = self._rng.choice(len(probs), p=probs, size=1)
# Convert to projector for state update
proj = np.zeros(len(probs), dtype=complex)
proj[sample] = 1 / np.sqrt(probs[sample])
# Rotate outcome to 0
reset = np.eye(len(probs))
reset[0, 0] = 0
reset[sample, sample] = 0
reset[0, sample] = 1
# compose with reset projection
reset = np.dot(reset, np.diag(proj))
return self.evolve(Operator(reset, input_dims=dims, output_dims=dims), qargs=qargs)
[documentos] @classmethod
def from_label(cls, label: str) -> Statevector:
"""Return a tensor product of Pauli X,Y,Z eigenstates.
.. list-table:: Single-qubit state labels
:header-rows: 1
* - Label
- Statevector
* - ``"0"``
- :math:`[1, 0]`
* - ``"1"``
- :math:`[0, 1]`
* - ``"+"``
- :math:`[1 / \\sqrt{2}, 1 / \\sqrt{2}]`
* - ``"-"``
- :math:`[1 / \\sqrt{2}, -1 / \\sqrt{2}]`
* - ``"r"``
- :math:`[1 / \\sqrt{2}, i / \\sqrt{2}]`
* - ``"l"``
- :math:`[1 / \\sqrt{2}, -i / \\sqrt{2}]`
Args:
label (string): a eigenstate string ket label (see table for
allowed values).
Returns:
Statevector: The N-qubit basis state density matrix.
Raises:
QiskitError: if the label contains invalid characters, or the
length of the label is larger than an explicitly
specified num_qubits.
"""
# Check label is valid
if re.match(r"^[01rl\-+]+$", label) is None:
raise QiskitError("Label contains invalid characters.")
# We can prepare Z-eigenstates by converting the computational
# basis bit-string to an integer and preparing that unit vector
# However, for X-basis states, we will prepare a Z-eigenstate first
# then apply Hadamard gates to rotate 0 and 1s to + and -.
z_label = label
xy_states = False
if re.match("^[01]+$", label) is None:
# We have X or Y eigenstates so replace +,r with 0 and
# -,l with 1 and prepare the corresponding Z state
xy_states = True
z_label = z_label.replace("+", "0")
z_label = z_label.replace("r", "0")
z_label = z_label.replace("-", "1")
z_label = z_label.replace("l", "1")
# Initialize Z eigenstate vector
num_qubits = len(label)
data = np.zeros(1 << num_qubits, dtype=complex)
pos = int(z_label, 2)
data[pos] = 1
state = Statevector(data)
if xy_states:
# Apply hadamards to all qubits in X eigenstates
x_mat = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2)
# Apply S.H to qubits in Y eigenstates
y_mat = np.dot(np.diag([1, 1j]), x_mat)
for qubit, char in enumerate(reversed(label)):
if char in ["+", "-"]:
state = state.evolve(x_mat, qargs=[qubit])
elif char in ["r", "l"]:
state = state.evolve(y_mat, qargs=[qubit])
return state
[documentos] @staticmethod
def from_int(i: int, dims: int | tuple | list) -> Statevector:
"""Return a computational basis statevector.
Args:
i (int): the basis state element.
dims (int or tuple or list): The subsystem dimensions of the statevector
(See additional information).
Returns:
Statevector: The computational basis state :math:`|i\\rangle`.
Additional Information:
The ``dims`` kwarg can be an integer or an iterable of integers.
* ``Iterable`` -- the subsystem dimensions are the values in the list
with the total number of subsystems given by the length of the list.
* ``Int`` -- the integer specifies the total dimension of the
state. If it is a power of two the state will be initialized
as an N-qubit state. If it is not a power of two the state
will have a single d-dimensional subsystem.
"""
size = np.prod(dims)
state = np.zeros(size, dtype=complex)
state[i] = 1.0
return Statevector(state, dims=dims)
[documentos] @classmethod
def from_instruction(cls, instruction: Instruction | QuantumCircuit) -> Statevector:
"""Return the output statevector of an instruction.
The statevector is initialized in the state :math:`|{0,\\ldots,0}\\rangle` of the
same number of qubits as the input instruction or circuit, evolved
by the input instruction, and the output statevector returned.
Args:
instruction (qiskit.circuit.Instruction or QuantumCircuit): instruction or circuit
Returns:
Statevector: The final statevector.
Raises:
QiskitError: if the instruction contains invalid instructions for
the statevector simulation.
"""
# Convert circuit to an instruction
if isinstance(instruction, QuantumCircuit):
instruction = instruction.to_instruction()
# Initialize an the statevector in the all |0> state
init = np.zeros(2**instruction.num_qubits, dtype=complex)
init[0] = 1.0
vec = Statevector(init, dims=instruction.num_qubits * (2,))
return Statevector._evolve_instruction(vec, instruction)
[documentos] def to_dict(self, decimals: None | int = None) -> dict:
r"""Convert the statevector to dictionary form.
This dictionary representation uses a Ket-like notation where the
dictionary keys are qudit strings for the subsystem basis vectors.
If any subsystem has a dimension greater than 10 comma delimiters are
inserted between integers so that subsystems can be distinguished.
Args:
decimals (None or int): the number of decimal places to round
values. If None no rounding is done
(Default: None).
Returns:
dict: the dictionary form of the Statevector.
Example:
The ket-form of a 2-qubit statevector
:math:`|\psi\rangle = |-\rangle\otimes |0\rangle`
.. code-block::
from qiskit.quantum_info import Statevector
psi = Statevector.from_label('-0')
print(psi.to_dict())
.. parsed-literal::
{'00': (0.7071067811865475+0j), '10': (-0.7071067811865475+0j)}
For non-qubit subsystems the integer range can go from 0 to 9. For
example in a qutrit system
.. code-block::
import numpy as np
from qiskit.quantum_info import Statevector
vec = np.zeros(9)
vec[0] = 1 / np.sqrt(2)
vec[-1] = 1 / np.sqrt(2)
psi = Statevector(vec, dims=(3, 3))
print(psi.to_dict())
.. parsed-literal::
{'00': (0.7071067811865475+0j), '22': (0.7071067811865475+0j)}
For large subsystem dimensions delimiters are required. The
following example is for a 20-dimensional system consisting of
a qubit and 10-dimensional qudit.
.. code-block::
import numpy as np
from qiskit.quantum_info import Statevector
vec = np.zeros(2 * 10)
vec[0] = 1 / np.sqrt(2)
vec[-1] = 1 / np.sqrt(2)
psi = Statevector(vec, dims=(2, 10))
print(psi.to_dict())
.. parsed-literal::
{'00': (0.7071067811865475+0j), '91': (0.7071067811865475+0j)}
"""
return self._vector_to_dict(
self.data, self._op_shape.dims_l(), decimals=decimals, string_labels=True
)
@staticmethod
def _evolve_operator(statevec, oper, qargs=None):
"""Evolve a qudit statevector"""
new_shape = statevec._op_shape.compose(oper._op_shape, qargs=qargs)
if qargs is None:
# Full system evolution
statevec._data = np.dot(oper._data, statevec._data)
statevec._op_shape = new_shape
return statevec
# Get transpose axes
num_qargs = statevec._op_shape.num_qargs[0]
indices = [num_qargs - 1 - i for i in reversed(qargs)]
axes = indices + [i for i in range(num_qargs) if i not in indices]
axes_inv = np.argsort(axes).tolist()
# Calculate contraction dimensions
contract_dim = oper._op_shape.shape[1]
contract_shape = (contract_dim, statevec._op_shape.shape[0] // contract_dim)
# Reshape and transpose input array for contraction
tensor = np.transpose(
np.reshape(statevec.data, statevec._op_shape.tensor_shape),
axes,
)
tensor_shape = tensor.shape
# Perform contraction
tensor = np.reshape(
np.dot(oper.data, np.reshape(tensor, contract_shape)),
tensor_shape,
)
# Transpose back to original subsystem spec and flatten
statevec._data = np.reshape(np.transpose(tensor, axes_inv), new_shape.shape[0])
# Update dimension
statevec._op_shape = new_shape
return statevec
@staticmethod
def _evolve_instruction(statevec, obj, qargs=None):
"""Update the current Statevector by applying an instruction."""
from qiskit.circuit.reset import Reset
from qiskit.circuit.barrier import Barrier
# pylint complains about a cyclic import since the following Initialize file
# imports the StatePreparation, which again requires the Statevector (this file),
# but as this is a local import, it's not actually an issue and can be ignored
# pylint: disable=cyclic-import
from qiskit.extensions.quantum_initializer.initializer import Initialize
mat = Operator._instruction_to_matrix(obj)
if mat is not None:
# Perform the composition and inplace update the current state
# of the operator
return Statevector._evolve_operator(statevec, Operator(mat), qargs=qargs)
# Special instruction types
if isinstance(obj, Reset):
statevec._data = statevec.reset(qargs)._data
return statevec
if isinstance(obj, Barrier):
return statevec
if isinstance(obj, Initialize):
# state is initialized to labels in the initialize object
if all(isinstance(param, str) for param in obj.params):
initialization = Statevector.from_label("".join(obj.params))._data
# state is initialized to an integer
# here we're only checking the length as (1) a length-1 object necessarily means the
# state is described by an integer (as labels were already covered) and (2) the int
# was cast to a complex and we cannot do an int typecheck anyways
elif len(obj.params) == 1:
state = int(np.real(obj.params[0]))
initialization = Statevector.from_int(state, (2,) * obj.num_qubits)._data
# state is initialized to the statevector
else:
initialization = np.asarray(obj.params, dtype=complex)
if qargs is None:
statevec._data = initialization
else:
# if we act on a subsystem we first need to reset and then apply the
# state preparation
statevec._data = statevec.reset(qargs)._data
mat = np.zeros((2 ** len(qargs), 2 ** len(qargs)), dtype=complex)
mat[:, 0] = initialization
statevec = Statevector._evolve_operator(statevec, Operator(mat), qargs=qargs)
return statevec
# If the instruction doesn't have a matrix defined we use its
# circuit decomposition definition if it exists, otherwise we
# cannot compose this gate and raise an error.
if obj.definition is None:
raise QiskitError(f"Cannot apply Instruction: {obj.name}")
if not isinstance(obj.definition, QuantumCircuit):
raise QiskitError(
"{} instruction definition is {}; expected QuantumCircuit".format(
obj.name, type(obj.definition)
)
)
if obj.definition.global_phase:
statevec._data *= np.exp(1j * float(obj.definition.global_phase))
qubits = {qubit: i for i, qubit in enumerate(obj.definition.qubits)}
for instruction in obj.definition:
if instruction.clbits:
raise QiskitError(
f"Cannot apply instruction with classical bits: {instruction.operation.name}"
)
# Get the integer position of the flat register
if qargs is None:
new_qargs = [qubits[tup] for tup in instruction.qubits]
else:
new_qargs = [qargs[qubits[tup]] for tup in instruction.qubits]
Statevector._evolve_instruction(statevec, instruction.operation, qargs=new_qargs)
return statevec