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

Support controlled-SWAP's in kernel builder #924

Merged
merged 10 commits into from
Dec 18, 2023
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
1 change: 1 addition & 0 deletions docs/sphinx/api/languages/python_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Program Construction
.. automethod:: r1
.. automethod:: cr1
.. automethod:: swap
.. automethod:: cswap
.. automethod:: exp_pauli
.. automethod:: mx
.. automethod:: my
Expand Down
1 change: 1 addition & 0 deletions docs/sphinx/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ def setup(app):
('cpp:identifier', 'BinarySymplecticForm'),
('cpp:identifier', 'CountsDictionary'),
('cpp:identifier', 'QuakeValueOrNumericType'),
('cpp:identifier', 'cudaq::ctrl'),
('py:class', 'function'),
('py:class', 'type'),
('py:class', 'cudaq::spin_op'),
Expand Down
74 changes: 74 additions & 0 deletions python/runtime/cudaq/builder/py_kernel_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,80 @@ target qubit/s.
# SWAP their states, resulting in the transformation: `|10> -> |01>`.
kernel.swap(first, second))#")

/// @brief Bind the controlled-SWAP gate with the controls provided in a
/// register of qubit/s.
.def(
"cswap",
[](kernel_builder<> &self, const QuakeValue &control,
const QuakeValue &first, const QuakeValue &second) {
return self.swap<cudaq::ctrl>(control, first, second);
},
py::arg("control"), py::arg("first"), py::arg("second"),
R"#(Perform a controlled-SWAP operation between two qubits,
if and only if the state of the provided `control` qubit/s is 1.

Args:
control (:class:`QuakeValue`): The control qubit/s for the operation.
first (:class:`QuakeValue`): The target qubit of the operation.
Its state will swap with the `second` qubit, based on the state of the
input `control`.
second (:class:`QuakeValue`): The target qubit of the operation.
Its state will swap with the `first` qubit, based on the state of the
input `control`.

.. code-block:: python

# Example:
kernel = cudaq.make_kernel()
# Allocate control qubit/s to the `kernel`.
control = kernel.qalloc(2)
# Allocate target qubits to the `kernel`.
targets = kernel.qalloc(2)
# Place the 0th target qubit in the 1-state.
kernel.x(targets[0])
# Place our control/s in the 1-state.
kernel.x(control)
# Since the `control` is all in the 1-state, our target
# states should undergo a SWAP.
kernel.cswap(control, targets[0], targets[1]))#")
/// @brief Bind the controlled-SWAP gate with the controls provided in a
/// vector.
.def(
"cswap",
[](kernel_builder<> &self, const std::vector<QuakeValue> controls,
const QuakeValue &first, const QuakeValue &second) {
return self.swap<cudaq::ctrl>(controls, first, second);
},
py::arg("controls"), py::arg("first"), py::arg("second"),
R"#(Perform a controlled-SWAP operation between two qubits,
if and only if the states of all provided `control` qubits are 1.

Args:
controls (list[QuakeValue]): The list of control qubits for the operation.
first (:class:`QuakeValue`): The target qubit of the operation.
Its state will swap with the `second` qubit, based on the state of the
input `controls`.
second (:class:`QuakeValue`): The target qubit of the operation.
Its state will swap with the `first` qubit, based on the state of the
input `controls`.

.. code-block:: python

# Example:
kernel = cudaq.make_kernel()
# Allocate control qubits to the `kernel`.
controls = [kernel.qalloc(), kernel.qalloc()]
# Allocate target qubits to the `kernel`.
targets = kernel.qalloc(2)
# Place the 0th target qubit in the 1-state.
kernel.x(targets[0])
# Place our controls in the 1-state.
kernel.x(controls[0])
kernel.x(controls[1])
# Since the `controls` are all in the 1-state, our target
# states should undergo a SWAP.
kernel.cswap(controls, targets[0], targets[1]))#")

/// @brief Allow for conditional statements on measurements.
.def(
"c_if",
Expand Down
54 changes: 43 additions & 11 deletions python/tests/compiler/ctrl_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ def test_kernel_ctrl_register():


# CHECK-LABEL: func.func @__nvqpp__mlirgen____nvqppBuilderKernel_{{.*}}() attributes {"cudaq-entrypoint"} {
# CHECK: %[[VAL_0:.*]] = quake.alloca !quake.veq<3>
# CHECK: %[[VAL_1:.*]] = quake.alloca !quake.veq<2>
# CHECK-DAG: %[[VAL_0:.*]] = quake.alloca !quake.veq<3>
# CHECK-DAG: %[[VAL_1:.*]] = quake.alloca !quake.veq<2>
# CHECK: %[[VAL_2:.*]] = quake.extract_ref %[[VAL_1]][0] : (!quake.veq<2>) -> !quake.ref
# CHECK: %[[VAL_3:.*]] = quake.extract_ref %[[VAL_1]][1] : (!quake.veq<2>) -> !quake.ref
# CHECK: quake.h {{\[}}%[[VAL_0]]] %[[VAL_2]] : (!quake.veq<3>, !quake.ref) -> ()
Expand Down Expand Up @@ -224,15 +224,15 @@ def test_kernel_rotation_ctrl_register():

# CHECK-LABEL: func.func @__nvqpp__mlirgen____nvqppBuilderKernel_{{.*}}(
# CHECK-SAME: %[[VAL_0:.*]]: !cc.stdvec<f64>) attributes {"cudaq-entrypoint"} {
# CHECK: %[[VAL_1:.*]] = arith.constant 3 : index
# CHECK: %[[VAL_2:.*]] = arith.constant 3.000000e+00 : f64
# CHECK: %[[VAL_3:.*]] = arith.constant 2.000000e+00 : f64
# CHECK: %[[VAL_4:.*]] = arith.constant 1.000000e+00 : f64
# CHECK: %[[VAL_5:.*]] = arith.constant 0.000000e+00 : f64
# CHECK: %[[VAL_6:.*]] = arith.constant 1 : index
# CHECK: %[[VAL_7:.*]] = arith.constant 0 : index
# CHECK: %[[VAL_8:.*]] = quake.alloca !quake.veq<3>
# CHECK: %[[VAL_9:.*]] = quake.alloca !quake.veq<2>
# CHECK-DAG: %[[VAL_1:.*]] = arith.constant 3 : index
# CHECK-DAG: %[[VAL_2:.*]] = arith.constant 3.000000e+00 : f64
# CHECK-DAG: %[[VAL_3:.*]] = arith.constant 2.000000e+00 : f64
# CHECK-DAG: %[[VAL_4:.*]] = arith.constant 1.000000e+00 : f64
# CHECK-DAG: %[[VAL_5:.*]] = arith.constant 0.000000e+00 : f64
# CHECK-DAG: %[[VAL_6:.*]] = arith.constant 1 : index
# CHECK-DAG: %[[VAL_7:.*]] = arith.constant 0 : index
# CHECK-DAG: %[[VAL_8:.*]] = quake.alloca !quake.veq<3>
# CHECK-DAG: %[[VAL_9:.*]] = quake.alloca !quake.veq<2>
# CHECK: %[[VAL_10:.*]] = quake.extract_ref %[[VAL_9]][0] : (!quake.veq<2>) -> !quake.ref
# CHECK: %[[VAL_11:.*]] = quake.extract_ref %[[VAL_9]][1] : (!quake.veq<2>) -> !quake.ref
# CHECK: %[[VAL_12:.*]] = cc.loop while ((%[[VAL_13:.*]] = %[[VAL_7]]) -> (index)) {
Expand Down Expand Up @@ -268,6 +268,38 @@ def test_kernel_rotation_ctrl_register():
# CHECK: return
# CHECK: }


def test_ctrl_swap():
"""
Tests the compilation of the various overloads of `cswap` gates.
"""
kernel = cudaq.make_kernel()
controls_vector = [kernel.qalloc() for _ in range(3)]
controls_register = kernel.qalloc(3)
first = kernel.qalloc()
second = kernel.qalloc()

kernel.cswap(controls_vector, first, second)
kernel.cswap(controls_register, first, second)
kernel.cswap([controls_vector[0], controls_vector[1], controls_register],
first, second)

print(kernel)


# CHECK-LABEL: func.func @__nvqpp__mlirgen____nvqppBuilderKernel_{{.*}}() attributes {"cudaq-entrypoint"} {
# CHECK-DAG: %[[VAL_0:.*]] = quake.alloca !quake.ref
# CHECK-DAG: %[[VAL_1:.*]] = quake.alloca !quake.ref
# CHECK-DAG: %[[VAL_2:.*]] = quake.alloca !quake.ref
# CHECK-DAG: %[[VAL_3:.*]] = quake.alloca !quake.veq<3>
# CHECK-DAG: %[[VAL_4:.*]] = quake.alloca !quake.ref
# CHECK-DAG: %[[VAL_5:.*]] = quake.alloca !quake.ref
# CHECK: quake.swap {{\[}}%[[VAL_0]], %[[VAL_1]], %[[VAL_2]]] %[[VAL_4]], %[[VAL_5]] : (!quake.ref, !quake.ref, !quake.ref, !quake.ref, !quake.ref) -> ()
# CHECK: quake.swap {{\[}}%[[VAL_3]]] %[[VAL_4]], %[[VAL_5]] : (!quake.veq<3>, !quake.ref, !quake.ref) -> ()
# CHECK: quake.swap {{\[}}%[[VAL_0]], %[[VAL_1]], %[[VAL_3]]] %[[VAL_4]], %[[VAL_5]] : (!quake.ref, !quake.ref, !quake.veq<3>, !quake.ref, !quake.ref) -> ()
# CHECK: return
# CHECK: }

# leave for gdb debugging
if __name__ == "__main__":
loc = os.path.abspath(__file__)
Expand Down
78 changes: 75 additions & 3 deletions python/tests/unittests/test_kernel_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# the kernel builder.

import pytest
import random
import numpy as np

import cudaq
Expand Down Expand Up @@ -579,9 +580,80 @@ def test_crz_gate():
assert counts["0010011"] == 1000


# FIXME
def test_cswap_gate():
pass
@pytest.mark.parametrize("control_count", [1, 2, 3])
def test_cswap_gate_ctrl_list(control_count):
"""Tests the controlled-SWAP operation given a vector of control qubits."""
kernel = cudaq.make_kernel()
controls = [kernel.qalloc() for _ in range(control_count)]
first = kernel.qalloc()
second = kernel.qalloc()

kernel.x(first)
# All controls in the |0> state, no SWAP should occur.
kernel.cswap(controls, first, second)
# If we have multiple controls, place a random control qubit
# in the |1> state. This check ensures that our controlled
# SWAP's are performed if and only if all controls are in the
# |1> state.
if (len(controls) > 1):
random_index = random.randint(0, control_count - 1)
kernel.x(controls[random_index])
# Not all controls in the in |1>, no SWAP.
kernel.cswap(controls, first, second)
# Rotate that random control back to |0>.
kernel.x(controls[random_index])

# Now place all of the controls in |1>.
for control in controls:
kernel.x(control)
# Should now SWAP our `first` and `second` qubits.
kernel.cswap(controls, first, second)

counts = cudaq.sample(kernel)
print(counts)

controls_state = "1" * control_count
want_state = controls_state + "01"
assert counts[want_state] == 1000


def test_cswap_gate_mixed_ctrls():
"""
Tests the controlled-SWAP gate given a list of a mix of ctrl
qubits and registers.
"""
kernel = cudaq.make_kernel()
controls_vector = [kernel.qalloc() for _ in range(2)]
controls_register = kernel.qalloc(2)
first = kernel.qalloc()
second = kernel.qalloc()

# `first` in |1> state.
kernel.x(first)
# `controls_register` in |1> state.
kernel.x(controls_register)

# `controls_vector` in |0>, no SWAP.
kernel.cswap(controls_vector, first, second)
# `controls_register` in |1>, SWAP.
kernel.cswap(controls_register, first, second)
# Pass the vector and register as the controls. The vector is still in |0>, so
# no SWAP.
kernel.cswap([controls_vector[0], controls_vector[1], controls_register],
first, second)
# Place the vector in |1>, should now get a SWAP.
kernel.x(controls_vector[0])
kernel.x(controls_vector[1])
kernel.cswap([controls_vector[0], controls_vector[1], controls_register],
first, second)

counts = cudaq.sample(kernel)
print(counts)

controls_state = "1111"
# The SWAP's should make the targets end up back in |10>.
want_state = controls_state + "10"
assert counts[want_state] == 1000


def test_crx_control_list():
Expand Down
1 change: 1 addition & 0 deletions python/tests/unittests/test_qpp_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import cudaq


def test_cpu_only_target():
"""Tests the QPP-based CPU-only target"""

Expand Down
9 changes: 7 additions & 2 deletions python/tests/utils/target_env_var_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
# RUN: PYTHONPATH=../../ pytest -rP %s

import os

os.environ["CUDAQ_DEFAULT_SIMULATOR"] = "density-matrix-cpu"

import pytest

import cudaq


def test_default_target():
"""Tests the default target set by environment variable"""

Expand All @@ -31,13 +33,14 @@ def test_default_target():
assert '00' in result
assert '11' in result


def test_env_var_with_emulate():
"""Tests the target when emulating a hardware backend"""

assert ("density-matrix-cpu" == cudaq.get_target().name)
cudaq.set_target("quantinuum", emulate=True)
assert ("quantinuum" == cudaq.get_target().name)

kernel = cudaq.make_kernel()
qubits = kernel.qalloc(2)
kernel.h(qubits[0])
Expand All @@ -49,11 +52,12 @@ def test_env_var_with_emulate():
assert '00' in result
assert '11' in result


def test_target_override():
"""Tests the target set by environment variable is overridden by user setting"""

cudaq.set_target("qpp-cpu")
assert("qpp-cpu" == cudaq.get_target().name)
assert ("qpp-cpu" == cudaq.get_target().name)

kernel = cudaq.make_kernel()
qubits = kernel.qalloc(2)
Expand All @@ -66,6 +70,7 @@ def test_target_override():
assert '00' in result
assert '11' in result


os.environ.pop("CUDAQ_DEFAULT_SIMULATOR")

# leave for gdb debugging
Expand Down
7 changes: 5 additions & 2 deletions python/tests/utils/target_env_var_reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@
# RUN: PYTHONPATH=../../ pytest -rP %s

import os

os.environ["CUDAQ_DEFAULT_SIMULATOR"] = "density-matrix-cpu"

import pytest

import cudaq


def test_env_var_update():
"""Tests that if the environment variable does not take effect on-the-fly"""

os.environ["CUDAQ_DEFAULT_SIMULATOR"] = "qpp-cpu"
assert("qpp-cpu" != cudaq.get_target().name)
assert ("qpp-cpu" != cudaq.get_target().name)

cudaq.set_target("qpp-cpu")
assert("qpp-cpu" == cudaq.get_target().name)
assert ("qpp-cpu" == cudaq.get_target().name)

kernel = cudaq.make_kernel()
qubits = kernel.qalloc(2)
Expand All @@ -38,6 +40,7 @@ def test_env_var_update():
cudaq.reset_target()
assert ("density-matrix-cpu" == cudaq.get_target().name)


os.environ.pop("CUDAQ_DEFAULT_SIMULATOR")

# leave for gdb debugging
Expand Down
Loading
Loading