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

Add models.encodings.binary_encoder for real-valued data #1551

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,12 @@ For instance, the following two circuit generations are equivalent:
.. autofunction:: qibo.models.encodings.phase_encoder


Binary encoder
""""""""""""""

.. autofunction:: qibo.models.encodings.binary_encoder


Unary Encoder
"""""""""""""

Expand Down
93 changes: 77 additions & 16 deletions src/qibo/models/encodings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
def comp_basis_encoder(
basis_element: Union[int, str, list, tuple], nqubits: Optional[int] = None, **kwargs
):
"""Creates circuit that performs encoding of bitstrings into computational basis states.
"""Create circuit that performs encoding of bitstrings into computational basis states.

Args:
basis_element (int or str or list or tuple): bitstring to be encoded.
Expand All @@ -31,7 +31,7 @@ def comp_basis_encoder(
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.

Returns:
:class:`qibo.models.circuit.Circuit`: circuit encoding computational basis element.
:class:`qibo.models.circuit.Circuit`: Circuit encoding computational basis element.
"""
if not isinstance(basis_element, (int, str, list, tuple)):
raise_error(
Expand Down Expand Up @@ -74,7 +74,7 @@ def comp_basis_encoder(


def phase_encoder(data, rotation: str = "RY", **kwargs):
"""Creates circuit that performs the phase encoding of ``data``.
"""Create circuit that performs the phase encoding of ``data``.

Args:
data (ndarray or list): :math:`1`-dimensional array of phases to be loaded.
Expand All @@ -86,7 +86,7 @@ def phase_encoder(data, rotation: str = "RY", **kwargs):
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.

Returns:
:class:`qibo.models.circuit.Circuit`: circuit that loads ``data`` in phase encoding.
:class:`qibo.models.circuit.Circuit`: Circuit that loads ``data`` in phase encoding.
"""
if isinstance(data, list):
data = np.array(data)
Expand Down Expand Up @@ -116,8 +116,69 @@ def phase_encoder(data, rotation: str = "RY", **kwargs):
return circuit


def binary_encoder(data, **kwargs):
"""Create circuit that encodes real-valued ``data`` in all amplitudes of the computational basis.

``data`` has to be normalized with respect to the Hilbert-Schmidt norm.
Resulting circuit parametrizes ``data`` in Hopf coordinates in the
:math:`(2^{n} - 1)`-unit sphere.

Args:
data (ndarray): :math:`1`-dimensional array or length :math:`2^{n}`
to be loaded in the amplitudes of a :math:`n`-qubit quantum state.

Returns:
:class:`qibo.models.circuit.Circuit`: Circuit that loads ``data`` in binay encoding.
"""
nqubits = float(np.log2(len(data)))
if not nqubits.is_integer():
raise_error(ValueError, "`data` size must be a power of 2.")

nqubits = int(nqubits)
dims = 2**nqubits

base_strings = [f"{elem:0{nqubits}b}" for elem in range(dims)]
base_strings = np.reshape(base_strings, (-1, 2))
strings = [base_strings]
for _ in range(nqubits - 1):
new_row = [row[0] for row in base_strings]
base_strings = np.reshape(new_row, (-1, 2))
strings.append(base_strings)
strings = strings[::-1]

targets_and_controls = []
for pairs in strings:
for pair in pairs:
targets, controls, anticontrols = [], [], []
for k, (bit_0, bit_1) in enumerate(zip(pair[0], pair[1])):
if bit_0 == "0" and bit_1 == "0":
anticontrols.append(k)
elif bit_0 == "1" and bit_1 == "1":
controls.append(k)
elif bit_0 == "0" and bit_1 == "1":
targets.append(k)
targets_and_controls.append([targets, controls, anticontrols])

circuit = Circuit(nqubits, **kwargs)
for targets, controls, anticontrols in targets_and_controls:
gate_list = []
if len(anticontrols) > 0:
gate_list.append(gates.X(qubit) for qubit in anticontrols)
gate_list.append(
gates.RY(targets[0], 0.0).controlled_by(*(controls + anticontrols))
)
if len(anticontrols) > 0:
gate_list.append(gates.X(qubit) for qubit in anticontrols)
circuit.add(gate_list)

angles = _generate_rbs_angles(data, dims, "tree")
circuit.set_parameters(2 * angles)

return circuit


def unary_encoder(data, architecture: str = "tree", **kwargs):
"""Creates circuit that performs the (deterministic) unary encoding of ``data``.
"""Create circuit that performs the (deterministic) unary encoding of ``data``.

Args:
data (ndarray): :math:`1`-dimensional array of data to be loaded.
Expand All @@ -129,7 +190,7 @@ def unary_encoder(data, architecture: str = "tree", **kwargs):
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.

Returns:
:class:`qibo.models.circuit.Circuit`: circuit that loads ``data`` in unary representation.
:class:`qibo.models.circuit.Circuit`: Circuit that loads ``data`` in unary representation.
"""
if isinstance(data, list):
data = np.array(data)
Expand Down Expand Up @@ -173,7 +234,7 @@ def unary_encoder(data, architecture: str = "tree", **kwargs):
def unary_encoder_random_gaussian(
nqubits: int, architecture: str = "tree", seed=None, **kwargs
):
"""Creates a circuit that performs the unary encoding of a random Gaussian state.
"""Create a circuit that performs the unary encoding of a random Gaussian state.

At depth :math:`h` of the tree architecture, the angles :math:`\\theta_{k} \\in [0, 2\\pi]` of the the
gates :math:`RBS(\\theta_{k})` are sampled from the following probability density function:
Expand All @@ -197,7 +258,7 @@ def unary_encoder_random_gaussian(
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.

Returns:
:class:`qibo.models.circuit.Circuit`: circuit that loads a random Gaussian array in unary representation.
:class:`qibo.models.circuit.Circuit`: Circuit that loads a random Gaussian array in unary representation.

References:
1. A. Bouland, A. Dandapani, and A. Prakash, *A quantum spectral method for simulating
Expand Down Expand Up @@ -271,7 +332,7 @@ def entangling_layer(
closed_boundary: bool = False,
**kwargs,
):
"""Creates a layer of two-qubit, entangling gates.
"""Create a layer of two-qubit, entangling gates.

If the chosen gate is a parametrized gate, all phases are set to :math:`0.0`.

Expand Down Expand Up @@ -377,7 +438,7 @@ def entangling_layer(


def ghz_state(nqubits: int, **kwargs):
"""Generates an :math:`n`-qubit Greenberger-Horne-Zeilinger (GHZ) state that takes the form
"""Generate an :math:`n`-qubit Greenberger-Horne-Zeilinger (GHZ) state that takes the form

.. math::
\\ket{\\text{GHZ}} = \\frac{\\ket{0}^{\\otimes n} + \\ket{1}^{\\otimes n}}{\\sqrt{2}}
Expand Down Expand Up @@ -406,7 +467,7 @@ def ghz_state(nqubits: int, **kwargs):


def _generate_rbs_pairs(nqubits: int, architecture: str, **kwargs):
"""Generating list of indexes representing the RBS connections
"""Generate list of indexes representing the RBS connections.

Creates circuit with all RBS initialised with 0.0 phase.

Expand All @@ -420,8 +481,8 @@ def _generate_rbs_pairs(nqubits: int, architecture: str, **kwargs):
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.

Returns:
(:class:`qibo.models.circuit.Circuit`, list): circuit composed of :class:`qibo.gates.gates.RBS`
and list of indexes of target qubits per depth.
(:class:`qibo.models.circuit.Circuit`, list): Circuit composed of :class:`qibo.gates.gates.RBS`
and list of indexes of target qubits per depth.
"""

if architecture == "diagonal":
Expand Down Expand Up @@ -451,7 +512,7 @@ def _generate_rbs_pairs(nqubits: int, architecture: str, **kwargs):


def _generate_rbs_angles(data, nqubits: int, architecture: str):
"""Generating list of angles for RBS gates based on ``architecture``.
"""Generate list of angles for RBS gates based on ``architecture``.

Args:
data (ndarray, optional): :math:`1`-dimensional array of data to be loaded.
Expand All @@ -462,7 +523,7 @@ def _generate_rbs_angles(data, nqubits: int, architecture: str):
Defaults to ``tree``.

Returns:
list: list of phases for RBS gates.
list: List of phases for RBS gates.
"""
if architecture == "diagonal":
engine = _check_engine(data)
Expand Down Expand Up @@ -494,7 +555,7 @@ def _generate_rbs_angles(data, nqubits: int, architecture: str):


def _parametrized_two_qubit_gate(gate, q0, q1, params=None):
"""Returns two-qubit gate initialized with or without phases."""
"""Return two-qubit gate initialized with or without phases."""
if params is not None:
return gate(q0, q1, *params)

Expand Down
22 changes: 22 additions & 0 deletions tests/test_models_encodings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@

from qibo import Circuit, gates
from qibo.models.encodings import (
binary_encoder,
comp_basis_encoder,
entangling_layer,
ghz_state,
phase_encoder,
unary_encoder,
unary_encoder_random_gaussian,
)
from qibo.quantum_info.random_ensembles import random_statevector


def _gaussian(x, a, b, c):
Expand Down Expand Up @@ -102,6 +104,26 @@ def test_phase_encoder(backend, rotation, kind):
backend.assert_allclose(state, target)


@pytest.mark.parametrize("nqubits", [3, 4, 5])
def test_binary_encoder(backend, nqubits):
with pytest.raises(ValueError):
dims = 5
test = np.random.rand(dims)
test = backend.cast(test, dtype=test.dtype)
test = binary_encoder(test)

dims = 2**nqubits

target = backend.np.real(random_statevector(dims, backend=backend))
target /= np.linalg.norm(target)
target = backend.cast(target, dtype=np.float64)

circuit = binary_encoder(target)
state = backend.execute_circuit(circuit).state()

backend.assert_allclose(state, target)


@pytest.mark.parametrize("kind", [None, list])
@pytest.mark.parametrize("architecture", ["tree", "diagonal"])
@pytest.mark.parametrize("nqubits", [8])
Expand Down
Loading