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 comp_basis_encoder to models.encodings #1165

Merged
merged 14 commits into from
Feb 2, 2024
26 changes: 26 additions & 0 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,32 @@ Data Encoders

We provide a family of algorithms that encode classical data into quantum circuits.

Computational Basis Encoder
"""""""""""""""""""""""""""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a couple of words about this and the other encoders we already have? I remember I was trying to use them some time ago but I had a hard time understanding how to use them. A small example of how to encode data into circuits might help.

Given a bitstring :math:`b` of length :math:`n`, this encoder generates of a layer of Pauli-:math:`X``
renatomello marked this conversation as resolved.
Show resolved Hide resolved
gates that creates the quantum state :math:`|\,b\,\rangle`.

For instance, the following two circuit generations are equivalent:

.. testsetup::

from qibo import Circuit, gates
from qibo.models.encodings import comp_basis_encoder

.. testcode::

b = "101"
circuit_1 = comp_basis_encoder(b)

circuit_2 = Circuit(3)
circuit_2.add(gates.X(0))
circuit_2.add(gates.X(2))


.. autofunction:: qibo.models.encodings.comp_basis_encoder


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

Expand Down
60 changes: 60 additions & 0 deletions src/qibo/models/encodings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Module with functions that encode classical data into quantum circuits."""

import math
from typing import Optional, Union

import numpy as np
from scipy.stats import rv_continuous
Expand All @@ -10,6 +11,65 @@
from qibo.models.circuit import Circuit


def comp_basis_encoder(
basis_element: Union[int, str, list, tuple], nqubits: Optional[int] = None
):
"""Creates circuit that performs encoding of bitstrings into computational basis states.

Args:
basis_element (int or str or list or tuple): bitstring to be encoded.
If ``int``, ``nqubits`` must be specified.
If ``str``, must be composed of only :math:`0`s and :math:`1`s.
If ``list`` or ``tuple``, must be composed of :math:`0`s and
:math:`1`s as ``int`` or ``str``.
nqubits (int, optional): total number of qubits in the circuit.
If ``basis_element`` is ``int``, ``nqubits`` must be specified.
If ``nqubits`` is ``None``, ``nqubits`` defaults to length of ``basis_element``.
Defaults to ``None``.

Returns:
:class:`qibo.models.circuit.Circuit`: circuit encoding computational basis element.
"""
if not isinstance(basis_element, (int, str, list, tuple)):
raise_error(
TypeError,
"basis_element must be either type int or str or list or tuple, "
+ f"but it is type {type(basis_element)}.",
)

if isinstance(basis_element, (str, list, tuple)):
if any(elem not in ["0", "1", 0, 1] for elem in basis_element):
raise_error(ValueError, "all elements must be 0 or 1.")

if nqubits is not None and not isinstance(nqubits, int):
raise_error(
TypeError, f"nqubits must be type int, but it is type {type(nqubits)}."
)

if nqubits is None:
if isinstance(basis_element, int):
raise_error(
ValueError, f"nqubits must be specified when basis_element is type int."
)
else:
nqubits = len(basis_element)

if isinstance(basis_element, int):
basis_element = f"{basis_element:0{nqubits}b}"

if isinstance(basis_element, (str, tuple)):
basis_element = list(basis_element)

basis_element = list(map(int, basis_element))

circuit = Circuit(nqubits)
for qubit, elem in enumerate(basis_element):
if elem == 1:
circuit.add(gates.X(qubit))

return circuit


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

Expand Down
36 changes: 35 additions & 1 deletion tests/test_models_encodings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,48 @@
import pytest
from scipy.optimize import curve_fit

from qibo.models.encodings import unary_encoder, unary_encoder_random_gaussian
from qibo.models.encodings import (
comp_basis_encoder,
unary_encoder,
unary_encoder_random_gaussian,
)


def gaussian(x, a, b, c):
"""Gaussian used in the `unary_encoder_random_gaussian test"""
return np.exp(a * x**2 + b * x + c)


@pytest.mark.parametrize(
"basis_element", [5, "101", ["1", "0", "1"], [1, 0, 1], ("1", "0", "1"), (1, 0, 1)]
)
def test_comp_basis_encoder(backend, basis_element):
with pytest.raises(TypeError):
circuit = comp_basis_encoder(2.3)
with pytest.raises(ValueError):
circuit = comp_basis_encoder("0b001")
with pytest.raises(ValueError):
circuit = comp_basis_encoder("001", nqubits=2)
with pytest.raises(TypeError):
circuit = comp_basis_encoder("001", nqubits=3.1)
with pytest.raises(ValueError):
circuit = comp_basis_encoder(3)

zero = np.array([1, 0], dtype=complex)
one = np.array([0, 1], dtype=complex)
target = np.kron(one, np.kron(zero, one))
target = backend.cast(target, dtype=target.dtype)

if isinstance(basis_element, int):
state = comp_basis_encoder(basis_element, nqubits=3)
else:
state = comp_basis_encoder(basis_element)

state = backend.execute_circuit(state).state()

backend.assert_allclose(state, target)


@pytest.mark.parametrize("architecture", ["tree", "diagonal"])
@pytest.mark.parametrize("nqubits", [8])
def test_unary_encoder(backend, nqubits, architecture):
Expand Down
Loading