Skip to content

Commit

Permalink
Merge pull request #1379 from qiboteam/encodings
Browse files Browse the repository at this point in the history
Allow `Circuit` `kwargs` in `models.encodings`
  • Loading branch information
renatomello committed Jul 5, 2024
2 parents 45727e0 + 6fe8818 commit 98a310a
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 19 deletions.
41 changes: 28 additions & 13 deletions src/qibo/models/encodings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


def comp_basis_encoder(
basis_element: Union[int, str, list, tuple], nqubits: Optional[int] = None
basis_element: Union[int, str, list, tuple], nqubits: Optional[int] = None, **kwargs
):
"""Creates circuit that performs encoding of bitstrings into computational basis states.
Expand All @@ -26,6 +26,8 @@ def comp_basis_encoder(
If ``basis_element`` is ``int``, ``nqubits`` must be specified.
If ``nqubits`` is ``None``, ``nqubits`` defaults to length of ``basis_element``.
Defaults to ``None``.
kwargs (dict, optional): Additional arguments used to initialize a Circuit object.
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.
Returns:
:class:`qibo.models.circuit.Circuit`: circuit encoding computational basis element.
Expand Down Expand Up @@ -62,15 +64,15 @@ def comp_basis_encoder(

basis_element = list(map(int, basis_element))

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

return circuit


def phase_encoder(data, rotation: str = "RY"):
def phase_encoder(data, rotation: str = "RY", **kwargs):
"""Creates circuit that performs the phase encoding of ``data``.
Args:
Expand All @@ -79,6 +81,8 @@ def phase_encoder(data, rotation: str = "RY"):
If ``"RY"``, uses :class:`qibo.gates.gates.RY` as rotation.
If ``"RZ"``, uses :class:`qibo.gates.gates.RZ` as rotation.
Defaults to ``"RY"``.
kwargs (dict, optional): Additional arguments used to initialize a Circuit object.
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.
Returns:
:class:`qibo.models.circuit.Circuit`: circuit that loads ``data`` in phase encoding.
Expand All @@ -104,14 +108,14 @@ def phase_encoder(data, rotation: str = "RY"):
nqubits = len(data)
gate = getattr(gates, rotation.upper())

circuit = Circuit(nqubits)
circuit = Circuit(nqubits, **kwargs)
circuit.add(gate(qubit, 0.0) for qubit in range(nqubits))
circuit.set_parameters(data)

return circuit


def unary_encoder(data, architecture: str = "tree"):
def unary_encoder(data, architecture: str = "tree", **kwargs):
"""Creates circuit that performs the (deterministic) unary encoding of ``data``.
Args:
Expand All @@ -120,6 +124,8 @@ def unary_encoder(data, architecture: str = "tree"):
If ``diagonal``, uses a ladder-like structure.
If ``tree``, uses a binary-tree-based structure.
Defaults to ``tree``.
kwargs (dict, optional): Additional arguments used to initialize a Circuit object.
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.
Returns:
:class:`qibo.models.circuit.Circuit`: circuit that loads ``data`` in unary representation.
Expand Down Expand Up @@ -151,9 +157,9 @@ def unary_encoder(data, architecture: str = "tree"):

nqubits = len(data)

circuit = Circuit(nqubits)
circuit = Circuit(nqubits, **kwargs)
circuit.add(gates.X(nqubits - 1))
circuit_rbs, _ = _generate_rbs_pairs(nqubits, architecture=architecture)
circuit_rbs, _ = _generate_rbs_pairs(nqubits, architecture=architecture, **kwargs)
circuit += circuit_rbs

# calculating phases and setting circuit parameters
Expand All @@ -163,7 +169,9 @@ def unary_encoder(data, architecture: str = "tree"):
return circuit


def unary_encoder_random_gaussian(nqubits: int, architecture: str = "tree", seed=None):
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.
At depth :math:`h` of the tree architecture, the angles :math:`\\theta_{k} \\in [0, 2\\pi]` of the the
Expand All @@ -184,6 +192,8 @@ def unary_encoder_random_gaussian(nqubits: int, architecture: str = "tree", seed
seed (int or :class:`numpy.random.Generator`, optional): Either a generator of
random numbers or a fixed seed to initialize a generator. If ``None``,
initializes a generator with a random seed. Defaults to ``None``.
kwargs (dict, optional): Additional arguments used to initialize a Circuit object.
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.
Expand Down Expand Up @@ -239,9 +249,9 @@ def unary_encoder_random_gaussian(nqubits: int, architecture: str = "tree", seed
a=0, b=2 * math.pi, seed=local_state
)

circuit = Circuit(nqubits)
circuit = Circuit(nqubits, **kwargs)
circuit.add(gates.X(nqubits - 1))
circuit_rbs, pairs_rbs = _generate_rbs_pairs(nqubits, architecture)
circuit_rbs, pairs_rbs = _generate_rbs_pairs(nqubits, architecture, **kwargs)
circuit += circuit_rbs

phases = []
Expand All @@ -258,6 +268,7 @@ def entangling_layer(
architecture: str = "diagonal",
entangling_gate: Union[str, gates.Gate] = "CNOT",
closed_boundary: bool = False,
**kwargs,
):
"""Creates a layer of two-qubit, entangling gates.
Expand All @@ -273,6 +284,8 @@ def entangling_layer(
all phases are initialized as :math:`0.0`. Defaults to ``"CNOT"``.
closed_boundary (bool, optional): If ``True`` adds a closed-boundary condition
to the entangling layer. Defaults to ``False``.
kwargs (dict, optional): Additional arguments used to initialize a Circuit object.
For details, see the documentation of :class:`qibo.models.circuit.Circuit`.
Returns:
:class:`qibo.models.circuit.Circuit`: Circuit containing layer of two-qubit gates.
Expand Down Expand Up @@ -329,7 +342,7 @@ def entangling_layer(
# If gate is parametrized, sets all angles to 0.0
parameters = (0.0,) * (len(parameters) - 3) if len(parameters) > 2 else None

circuit = Circuit(nqubits)
circuit = Circuit(nqubits, **kwargs)

if architecture == "diagonal":
circuit.add(
Expand Down Expand Up @@ -362,7 +375,7 @@ def entangling_layer(
return circuit


def _generate_rbs_pairs(nqubits: int, architecture: str):
def _generate_rbs_pairs(nqubits: int, architecture: str, **kwargs):
"""Generating list of indexes representing the RBS connections
Creates circuit with all RBS initialised with 0.0 phase.
Expand All @@ -373,6 +386,8 @@ def _generate_rbs_pairs(nqubits: int, architecture: str):
If ``diagonal``, uses a ladder-like structure.
If ``tree``, uses a binary-tree-based structure.
Defaults to ``tree``.
kwargs (dict, optional): Additional arguments used to initialize a Circuit object.
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`
Expand All @@ -397,7 +412,7 @@ def _generate_rbs_pairs(nqubits: int, architecture: str):
[(nqubits - 1 - a, nqubits - 1 - b) for a, b in row] for row in pairs_rbs
]

circuit = Circuit(nqubits)
circuit = Circuit(nqubits, **kwargs)
for row in pairs_rbs:
for pair in row:
circuit.add(gates.RBS(*pair, 0.0, trainable=True))
Expand Down
32 changes: 26 additions & 6 deletions tests/test_models_encodings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
)


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

Expand All @@ -42,10 +42,11 @@ def test_comp_basis_encoder(backend, basis_element):
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 = (
comp_basis_encoder(basis_element, nqubits=3)
if isinstance(basis_element, int)
else comp_basis_encoder(basis_element)
)

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

Expand Down Expand Up @@ -171,7 +172,7 @@ def test_unary_encoder_random_gaussian(backend, nqubits, seed):
y, x = np.histogram(amplitudes, bins=50, density=True)
x = (x[:-1] + x[1:]) / 2

params, _ = curve_fit(gaussian, x, y)
params, _ = curve_fit(_gaussian, x, y)

stddev = np.sqrt(-1 / (2 * params[0]))
mean = stddev**2 * params[1]
Expand Down Expand Up @@ -255,3 +256,22 @@ def _helper_entangling_test(gate, qubit_0, qubit_1=None):
gate = gates.CNOT

return gate(qubit_0, qubit_1)


@pytest.mark.parametrize("density_matrix", [False, True])
def test_circuit_kwargs(density_matrix):
test = comp_basis_encoder(5, 7, density_matrix=density_matrix)
assert test.density_matrix is density_matrix

test = entangling_layer(5, density_matrix=density_matrix)
assert test.density_matrix is density_matrix

data = np.random.rand(5)
test = phase_encoder(data, density_matrix=density_matrix)
assert test.density_matrix is density_matrix

test = unary_encoder(data, "diagonal", density_matrix=density_matrix)
assert test.density_matrix is density_matrix

test = unary_encoder_random_gaussian(4, density_matrix=density_matrix)
assert test.density_matrix is density_matrix

0 comments on commit 98a310a

Please sign in to comment.