# Source code for pennylane.templates.state_preparations.mottonen

# Copyright 2018-2020 Xanadu Quantum Technologies Inc.

# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# Unless required by applicable law or agreed to in writing, software
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
r"""
Contains the MottonenStatePreparation template.
"""
import math
import numpy as np
from scipy import sparse

import pennylane as qml

from pennylane.templates.decorator import template
from pennylane.templates.utils import check_wires, check_shape, get_shape
from pennylane.variable import Variable

# pylint: disable=len-as-condition,arguments-out-of-order
def gray_code(rank):
"""Generates the Gray code of given rank.

Args:
rank (int): rank of the Gray code (i.e. number of bits)
"""

def gray_code_recurse(g, rank):
k = len(g)
if rank <= 0:
return

for i in range(k - 1, -1, -1):
char = "1" + g[i]
g.append(char)
for i in range(k - 1, -1, -1):
g[i] = "0" + g[i]

gray_code_recurse(g, rank - 1)

g = ["0", "1"]
gray_code_recurse(g, rank - 1)

return g

def _matrix_M_entry(row, col):
"""Returns one entry for the matrix that maps alpha to theta.

Args:
row (int): one-based row number
col (int): one-based column number

Returns:
(float): transformation matrix entry at given row and column
"""
# (col >> 1) ^ col is the Gray code of col
b_and_g = row & ((col >> 1) ^ col)
sum_of_ones = 0
while b_and_g > 0:
if b_and_g & 0b1:
sum_of_ones += 1

b_and_g = b_and_g >> 1

return (-1) ** sum_of_ones

def _compute_theta(alpha):
"""Calculates the rotation angles from the alpha vector.

Args:
alpha (array[float]): alpha parameters

Returns:
(array[float]): rotation angles theta
"""
k = np.log2(alpha.shape)
factor = 2 ** (-k)

theta = sparse.dok_matrix(alpha.shape, dtype=np.float64)  # type: sparse.dok_matrix

for row in range(alpha.shape):
# Use transpose of M:
entry = sum([_matrix_M_entry(col, row) * a for (col, _), a in alpha.items()])
entry *= factor
if abs(entry) > 1e-6:
theta[row, 0] = entry

return theta

def _uniform_rotation_dagger(gate, alpha, control_wires, target_wire):
"""Applies a given inverse rotation to the target qubit
that is uniformly controlled by the control qubits.

Args:
gate (~.Operation): gate to be applied, needs to have exactly
one parameter
alpha (array[float]): alpha parameters
control_wires (array[int]): wires that act as control
target_wire (int): wire that acts as target
"""

theta = _compute_theta(alpha)  # type: sparse.dok_matrix

gray_code_rank = len(control_wires)

if gray_code_rank == 0:
gate(theta[0, 0], wires=[target_wire])
return

code = gray_code(gray_code_rank)
num_selections = len(code)

control_indices = [
int(np.log2(int(code[i], 2) ^ int(code[(i + 1) % num_selections], 2)))
for i in range(num_selections)
]

for i, control_index in enumerate(control_indices):
gate(theta[i, 0], wires=[target_wire])
qml.CNOT(wires=[control_wires[control_index], target_wire])

def _uniform_rotation_z_dagger(alpha, control_wires, target_wire):
"""Applies the inverse of a Z rotation to the target qubit
that is uniformly controlled by the control qubits.

Args:
alpha (array[float]): alpha parameters
control_wires (array[int]): wires that act as control
target_wire (int): wire that acts as target
"""

_uniform_rotation_dagger(qml.RZ, alpha, control_wires, target_wire)

def _uniform_rotation_y_dagger(alpha, control_wires, target_wire):
"""Applies the inverse of a Y rotation to the target qubit
that is uniformly controlled by the control qubits.

Args:
alpha (array[float]): alpha parameters
control_wires (array[int]): wires that act as control
target_wire (int): wire that acts as target
"""

_uniform_rotation_dagger(qml.RY, alpha, control_wires, target_wire)

def _get_alpha_z(omega, n, k):
r"""Computes the rotation angles alpha for the Z rotations.

Args:
omega (float): phase of the input
n (int): total number of qubits
k (int): current qubit

Returns:
scipy.sparse.dok_matrix[np.float64]: a sparse vector representing :math:\alpha^z_k
"""
alpha_z_k = sparse.dok_matrix((2 ** (n - k), 1), dtype=np.float64)

for (i, _), om in omega.items():
i += 1
j = int(np.ceil(i * 2 ** (-k)))
s_condition = 2 ** (k - 1) * (2 * j - 1)
s_i = 1.0 if i > s_condition else -1.0
alpha_z_k[j - 1, 0] = alpha_z_k[j - 1, 0] + s_i * om / 2 ** (k - 1)

return alpha_z_k

def _get_alpha_y(a, n, k):
r"""Computes the rotation angles alpha for the Y rotations.

Args:
omega (float): phase of the input
n (int): total number of qubits
k (int): current qubit

Returns:
scipy.sparse.dok_matrix[np.float64]: a sparse vector representing :math:\alpha^y_k
"""
alpha = sparse.dok_matrix((2 ** (n - k), 1), dtype=np.float64)

numerator = sparse.dok_matrix((2 ** (n - k), 1), dtype=np.float64)
denominator = sparse.dok_matrix((2 ** (n - k), 1), dtype=np.float64)

for (i, _), e in a.items():
j = int(math.ceil((i + 1) / 2 ** k))
l = (i + 1) - (2 * j - 1) * 2 ** (k - 1)
is_part_numerator = 1 <= l <= 2 ** (k - 1)

if is_part_numerator:
numerator[j - 1, 0] += e * e
denominator[j - 1, 0] += e * e

for (j, _), e in numerator.items():
numerator[j, 0] = math.sqrt(e)
for (j, _), e in denominator.items():
denominator[j, 0] = 1 / math.sqrt(e)

pre_alpha = numerator.multiply(denominator)  # type: sparse.csr_matrix
for (j, _), e in pre_alpha.todok().items():
alpha[j, 0] = 2 * np.arcsin(e)

return alpha

[docs]@template
def MottonenStatePreparation(state_vector, wires):
r"""
Prepares an arbitrary state on the given wires using a decomposition into gates developed
by Möttönen et al. (Quantum Info. Comput., 2005).

The state is prepared via a sequence
of "uniformly controlled rotations". A uniformly controlled rotation on a target qubit is
composed from all possible controlled rotations on said qubit and can be used to address individual
elements of the state vector. In the work of Mottonen et al., the inverse of their state preparation
is constructed by first equalizing the phases of the state vector via uniformly controlled Z rotations
and then rotating the now real state vector into the direction of the state :math:|0\rangle via
uniformly controlled Y rotations.

This code is adapted from code written by Carsten Blank for PennyLane-Qiskit.

Args:
state_vector (array): Input array of shape (2^N,), where N is the number of wires
the state preparation acts on. N must be smaller or equal to the total
number of wires.
wires (Sequence[int]): sequence of qubit indices that the template acts on

Raises:
ValueError: if inputs do not have the correct format
"""

###############
# Input checks

wires = check_wires(wires)

n_wires = len(wires)
expected_shape = (2 ** n_wires,)
check_shape(
state_vector,
expected_shape,
msg="'state_vector' must be of shape {}; got {}."
"".format(expected_shape, get_shape(state_vector)),
)

# check if state_vector is normalized
if isinstance(state_vector, Variable):
state_vector_values = [s.val for s in state_vector]
norm = np.sum(np.abs(state_vector_values) ** 2)
else:
norm = np.sum(np.abs(state_vector) ** 2)
if not np.isclose(norm, 1.0, atol=1e-3):
raise ValueError("'state_vector' has to be of length 1.0, got {}".format(norm))

#######################

# Change ordering of indices, original code was for IBM machines
state_vector = np.array(state_vector).reshape( * n_wires).T.flatten()[:, np.newaxis]
state_vector = sparse.dok_matrix(state_vector)

wires = np.array(wires)

a = sparse.dok_matrix(state_vector.shape)
omega = sparse.dok_matrix(state_vector.shape)

for (i, j), v in state_vector.items():
if isinstance(v, Variable):
a[i, j] = np.absolute(v.val)
omega[i, j] = np.angle(v.val)
else:
a[i, j] = np.absolute(v)
omega[i, j] = np.angle(v)
# This code is directly applying the inverse of Carsten Blank's
# code to avoid inverting at the end

# Apply y rotations
for k in range(n_wires, 0, -1):
alpha_y_k = _get_alpha_y(a, n_wires, k)  # type: sparse.dok_matrix
control = wires[k:]
target = wires[k - 1]
_uniform_rotation_y_dagger(alpha_y_k, control, target)

# Apply z rotations
for k in range(n_wires, 0, -1):
alpha_z_k = _get_alpha_z(omega, n_wires, k)
control = wires[k:]
target = wires[k - 1]
if len(alpha_z_k) > 0:
_uniform_rotation_z_dagger(alpha_z_k, control, target)


Using PennyLane

Development

API