Skip to content

Commit

Permalink
WIP attempt at integrating OpenSquirrel
Browse files Browse the repository at this point in the history
  • Loading branch information
pablolh committed Nov 8, 2023
1 parent f5b956e commit 9fe7c77
Show file tree
Hide file tree
Showing 8 changed files with 631 additions and 645 deletions.
8 changes: 4 additions & 4 deletions examples/hqca_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@


def generate_circuit() -> str:
with Circuit(platform_name="spin-2", program_name="prgm1") as circuit:
kernel = circuit.init_kernel("new_kernel", 2)
kernel.hadamard(0)
kernel.cnot(0, 1)
with Circuit(number_of_qubits=2, program_name="prgm1") as circuit:
circuit.enter_section("my_section")
circuit.hadamard(0)
circuit.cnot(0, 1)

return circuit.content

Expand Down
29 changes: 14 additions & 15 deletions examples/qaoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,27 +74,27 @@ def qaoa_circuit(graph: Graph, beta: np.ndarray, gamma: np.ndarray) -> str:
Returns:
cQASM string representing the quantum circuit used to compute the energies in the QAOA algorithm.
"""
with Circuit(platform_name="spin-2", program_name="qaoa") as circuit:
init_kernel = circuit.init_kernel("initialize", graph.number_of_nodes())
with Circuit(number_of_qubits=graph.number_of_nodes(), program_name="qaoa") as circuit:
init_section = circuit.enter_section("initialize")
for i in graph.nodes:
init_kernel.prepz(i)
init_section.prepz(i)
for i in graph.nodes:
init_kernel.hadamard(i)
init_section.hadamard(i)

for i in range(P):
ug_kernel = circuit.init_kernel(f"U_gamma_{i + 1}", graph.number_of_nodes())
ug_section = circuit.enter_section(f"U_gamma_{i + 1}")
for edge in graph.edges():
ug_kernel.cnot(edge[0], edge[1])
ug_kernel.rz(edge[1], 2 * gamma[i])
ug_kernel.cnot(edge[0], edge[1])
ug_section.cnot(edge[0], edge[1])
ug_section.rz(edge[1], 2 * gamma[i])
ug_section.cnot(edge[0], edge[1])

ub_kernel = circuit.init_kernel(f"U_beta_{i + 1}", graph.number_of_nodes())
ub_section = circuit.enter_section(f"U_beta_{i + 1}")
for j in graph.nodes():
ub_kernel.rx(j, 2 * beta[i])
ub_section.rx(j, 2 * beta[i])

final_kernel = circuit.init_kernel("finalize", graph.number_of_nodes())
final_section = circuit.enter_section("finalize")
for i in graph.nodes():
final_kernel.measure(i)
final_section.measure(i)

return circuit.content

Expand Down Expand Up @@ -180,10 +180,9 @@ def finalize(list_of_measurements: Dict[int, List[Any]]) -> Dict[str, Any]:
print("=== Qaoa circuit ===", qaoa_circuit(GRAPH, beta, gamma))
# Now the beginning of the circuit always looks like
"""
# Generated by OpenQL 0.11.1 for program qaoa
version 1.0
version 3.0
qubits 10
qubit[10] q
"""
# independently of the actual qubits used.
# this also means that the results look like
Expand Down
11 changes: 5 additions & 6 deletions examples/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
from quantuminspire.sdk.models.circuit import Circuit
from quantuminspire.util.api.remote_backend import RemoteBackend

with Circuit(platform_name="spin-2", program_name="prgm1") as c:
k = c.init_kernel("new_kernel", 2)
k.x(0)
k.hadamard(1)
k.measure(0)
k.measure(1)
with Circuit(number_of_qubits=2, program_name="prgm1") as c:
c.enter_section("my_section")
c.x(0)
c.hadamard(1)
c.measure(0).measure(1)

print(c.content)

Expand Down
1,060 changes: 547 additions & 513 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ qi = "quantuminspire.cli.command_list:app"
[tool.poetry.dependencies]
python = "^3.8"
typer = {extras = ["all"], version = "^0.9.0"}
qutechopenql = "^0.11.1"
pydantic = "^1.10.9"
qi-compute-api-client = {git = "https://github.com/QuTech-Delft/compute-api-client.git", branch="0.9.0"}
qxelarator = {version = "^0.6.2", optional = true}
opensquirrel = "^0.0.5"

[tool.poetry.extras]
local = ["qxelarator"]
Expand Down
84 changes: 23 additions & 61 deletions quantuminspire/sdk/models/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,18 @@
from types import TracebackType
from typing import List, Optional, Type

import openql
import opensquirrel
from compute_api_client import AlgorithmType, CompileStage
from openql import Kernel, Platform, Program

from quantuminspire.sdk.models.base_algorithm import BaseAlgorithm


class Circuit(BaseAlgorithm):
"""A container object, interacting with OpenQL and storing cQASM internally.
A circuit wraps OpenQL to handle the boilerplate code for platform, program and kernels. These objects can still be
used.
"""

def __init__(self, platform_name: str, program_name: str) -> None:
super().__init__(platform_name, program_name)
self._output_dir = Path(__file__).parent.absolute() / "output"
openql.set_option("output_dir", str(self._output_dir))
self._openql_platform = Platform(self._platform_name, "none")
self._openql_program: Optional[Program] = None
self._openql_kernels: List[Kernel] = []
"""A container object, interacting with OpenSquirrel and building a (compiled) cQASM 3.0 string."""

def __init__(self, number_of_qubits: int, program_name: str) -> None:
super().__init__(f"{number_of_qubits}_qubits", program_name)
self._opensquirrel_circuit_builder = opensquirrel.CircuitBuilder(opensquirrel.DefaultGates, number_of_qubits)
self._cqasm: str = ""

@property
Expand All @@ -46,65 +37,36 @@ def __exit__(
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
) -> bool:
self.finalize()
return True
# return True ########## Why are exceptions suppressed here?

def initialize(self) -> None:
"""Initialize the quantum circuit."""

def finalize(self) -> None:
"""Finalize the quantum circuit.
After finishing writing the quantum circuit various actions are performed to generate the actual cQASM circuit.
First, the used number of qubits is determined, based on the various kernels. It is assumed that the qubits
will be reused over the various kernels. This creates an OpenQL program, to which the various kernels are
added. Finally, the program is compiled and the generated cQASM file is processed to an internal variable.
After finishing writing the quantum circuit using the circuit builder interface, the resulting program is compiled (or not)
by OpenSquirrel. Finally, the compiled program is written to a cQASM3 string to an internal variable.
"""
self._openql_program = openql.Program(self._program_name, self._openql_platform, self.max_number_of_qubits)
for kernel in self._openql_kernels:
self._openql_program.add_kernel(kernel)
self._openql_program.get_compiler().set_option("initialqasmwriter.cqasm_version", "1.0")
self._openql_program.get_compiler().set_option("initialqasmwriter.with_metadata", "no")
self._openql_program.compile()
self._cqasm = self._process_cqasm_file()

@property
def max_number_of_qubits(self) -> int:
"""Determine the number of qubits over the various kernels.
self._cqasm = str(self._opensquirrel_circuit_builder.to_circuit())

Returns:
The maximum number of qubits used in the kernels, assuming that the qubits can be reused.
"""
return int(max((kernel.qubit_count for kernel in self._openql_kernels), default=0))
def enter_section(self, name: str) -> opensquirrel.CircuitBuilder:
"""Enter a new section in the circuit, by skipping a line and adding a comment in the output cQASM3 string.
def _process_cqasm_file(self) -> str:
"""Read and remove the generated cQASM file.
Returns:
The content of the OpenQL generated cQASM file.
"""
cqasm_file = self._output_dir / f"{self._program_name}.qasm"
with open(cqasm_file, encoding="utf-8") as file_pointer:
cqasm = file_pointer.read()
Path.unlink(cqasm_file)
return cqasm

def init_kernel(self, name: str, number_of_qubits: int) -> Kernel:
"""Initialize an OpenQL kernel.
A new OpenQL kernel is created and added to an internal list (ordered) of kernels. This list will be used to
compile the final program (in order).
Those "sections" are just here to help humans read and understand the output cQASM3 strings.
They have no semantic meaning in the cQASM3 language.
Args:
name: Name of the kernel.
number_of_qubits: Number of qubits used in the kernel.
name: Name of the section of the quantum circuit.
Returns:
The OpenQL kernel.
The OpenSquirrel circuit builder.
"""
kernel = Kernel(name, self._openql_platform, number_of_qubits)
self._openql_kernels.append(kernel)
return kernel

def add_kernel(self, kernel: Kernel) -> None:
"""Add an existing kernel to the list of kernels."""
self._openql_kernels.append(kernel)
self._opensquirrel_circuit_builder.comment(name)

return self._opensquirrel_circuit_builder


def __getattr__(self, attr):
return self._opensquirrel_circuit_builder.__getattr__(attr)
80 changes: 36 additions & 44 deletions tests/sdk/test_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,82 +8,74 @@

MOCK_QUANTUM_CIRCUIT = "quantum circuit"


@pytest.fixture
def openql(mocker: MockerFixture) -> Generator[MagicMock, None, None]:
yield mocker.patch("quantuminspire.sdk.models.circuit.openql")


@pytest.fixture
def mock_file(mocker: MockerFixture) -> None:
mocker.patch("quantuminspire.sdk.models.circuit.open", mocker.mock_open(read_data=MOCK_QUANTUM_CIRCUIT))
mocker.patch("quantuminspire.sdk.models.circuit.Path.unlink")

def opensquirrel(mocker: MockerFixture) -> Generator[MagicMock, None, None]:
yield mocker.patch("quantuminspire.sdk.models.circuit.opensquirrel")

def test_create(openql: MagicMock) -> None:
_ = Circuit(platform_name="platform", program_name="program")
openql.set_option.assert_called_once()
def test_create(opensquirrel: MagicMock) -> None:
_ = Circuit(number_of_qubits=5, program_name="program")
opensquirrel.CircuitBuilder.assert_called_once()


def test_get_program_name(openql: MagicMock, mock_file: None) -> None:
with Circuit(platform_name="platform", program_name="program") as c:
def test_get_program_name(opensquirrel: MagicMock) -> None:
with Circuit(number_of_qubits=5, program_name="program") as c:
pass

assert c.program_name == "program"


def test_get_platform_name(openql: MagicMock, mock_file: None) -> None:
with Circuit(platform_name="platform", program_name="program") as c:
def test_get_platform_name(opensquirrel: MagicMock) -> None:
with Circuit(number_of_qubits=5, program_name="program") as c:
pass

assert c.platform_name == "platform"
assert c.platform_name == "5_qubits"


def test_get_content_type(openql: MagicMock, mock_file: None) -> None:
with Circuit(platform_name="platform", program_name="program") as c:
def test_get_content_type(opensquirrel: MagicMock) -> None:
with Circuit(number_of_qubits=5, program_name="program") as c:
pass

assert c.content_type == "quantum"


def test_get_compile_stage(openql: MagicMock, mock_file: None) -> None:
with Circuit(platform_name="platform", program_name="program") as c:
def test_get_compile_stage(opensquirrel: MagicMock) -> None:
with Circuit(number_of_qubits=5, program_name="program") as c:
pass

assert c.compile_stage == "none"


def test_create_empty_circuit(openql: MagicMock, mock_file: None) -> None:
with Circuit(platform_name="platform", program_name="program") as c:
def test_create_empty_circuit(opensquirrel: MagicMock) -> None:
opensquirrel.CircuitBuilder().to_circuit.return_value = MOCK_QUANTUM_CIRCUIT

with Circuit(number_of_qubits=5, program_name="program") as c:
pass

openql.Program().compile.assert_called_once()
opensquirrel.CircuitBuilder().to_circuit.assert_called_once()
assert c.content == MOCK_QUANTUM_CIRCUIT


def test_create_circuit_with_kernel(openql: MagicMock, mock_file: None) -> None:
with Circuit(platform_name="platform", program_name="program") as c:
k = c.init_kernel("kernel1", 2)
k.x(0)
def test_create_circuit_with_kernel(opensquirrel: MagicMock) -> None:
with Circuit(number_of_qubits=5, program_name="program") as c:
c.enter_section("section1")
c.x(0)

openql.Program().add_kernel.assert_called_once()
assert c.max_number_of_qubits == 2
opensquirrel.CircuitBuilder().comment.assert_called_once()


def test_create_circuit_with_multiple_kernels(openql: MagicMock, mock_file: None) -> None:
with Circuit(platform_name="platform", program_name="program") as c:
k = c.init_kernel("kernel1", 2)
k.x(0)
_ = c.init_kernel("kernel2", 3)
def test_create_circuit_with_multiple_kernels(opensquirrel: MagicMock) -> None:
with Circuit(number_of_qubits=5, program_name="program") as c:
c.enter_section("section1")
c.x(0)
c.enter_section("section2")

assert len(openql.Program().add_kernel.mock_calls) == 2
assert c.max_number_of_qubits == 3
assert len(opensquirrel.CircuitBuilder().comment.mock_calls) == 2


def test_create_circuit_reuse_kernel(openql: MagicMock, mock_file: None) -> None:
with Circuit(platform_name="platform", program_name="program") as c:
k = c.init_kernel("kernel1", 2)
k.x(0)
c.add_kernel(k)
def test_create_circuit_reuse_kernel(opensquirrel: MagicMock) -> None:
with Circuit(number_of_qubits=5, program_name="program") as c:
s1 = c.enter_section("section1")
s1.x(0)
c.enter_section("section2")

assert len(openql.Program().add_kernel.mock_calls) == 2
assert len(opensquirrel.CircuitBuilder().comment.mock_calls) == 2
2 changes: 1 addition & 1 deletion tests/util/api/test_local_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def test_local_backend_run_with_hybrid_algorithm(local_backend: MockLocalBackend


def test_local_backend_run_with_quantum_algorithm(local_backend: MockLocalBackend) -> None:
algorithm = Circuit("test", "Test")
algorithm = Circuit(number_of_qubits = 3, program_name = "Test")
job_id = local_backend.run(algorithm, 0)
local_backend.run_hybrid.assert_called_once()
results = local_backend.get_results(job_id)
Expand Down

0 comments on commit 9fe7c77

Please sign in to comment.