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

Allow Circuit kwargs in models.encodings #1379

Merged
merged 2 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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