Skip to content

Commit

Permalink
Merge pull request #14 from daniel-mills-cqc/code_refactor
Browse files Browse the repository at this point in the history
Code refactor
  • Loading branch information
Mr authored Apr 10, 2024
2 parents 3f0f061 + 12c8c1e commit 0a2717e
Show file tree
Hide file tree
Showing 10 changed files with 850 additions and 493 deletions.
51 changes: 7 additions & 44 deletions .github/workflows/run_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,55 +10,22 @@ on:

jobs:

Run-Tests-Ubuntu:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11"]

steps:

- name: Checkout pytket-mbqc
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'

- name: Install pytket-mbqc
run: pip install pytket-mbqc-py/
Run-Tests:

- name: Run Tests
run: pytest --cov-report term-missing:skip-covered --cov=pytket-mbqc-py/pytket_mbqc_py/ pytket-mbqc-py/tests/ --durations=10

- name: Type Check
run: mypy pytket-mbqc-py/pytket_mbqc_py/ --warn-unused-ignores

- name: Lint Check
uses: chartboost/ruff-action@v1
with:
args: check

- name: Format Check
uses: chartboost/ruff-action@v1
with:
args: format --check

Run-Tests-Mac:
runs-on: macos
strategy:
matrix:
python-version: ["3.10", "3.11"]
os: [ubuntu-latest, macos-latest]

runs-on: ${{ matrix.os }}

steps:

- name: Checkout pytket-mbqc
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
Expand All @@ -73,11 +40,7 @@ jobs:
run: mypy pytket-mbqc-py/pytket_mbqc_py/ --warn-unused-ignores

- name: Lint Check
uses: chartboost/ruff-action@v1
with:
args: check
run: ruff check

- name: Format Check
uses: chartboost/ruff-action@v1
with:
args: format --check
run: ruff format --check
652 changes: 398 additions & 254 deletions pytket-mbqc-py/poetry.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion pytket-mbqc-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ readme = "README.md"
[tool.poetry.dependencies]
python = "<3.12,>=3.10"
notebook = "^6.5.0"
pytket-quantinuum = "^0.29.0"
pytket-quantinuum = "^0.32.0"
pytket_pecos = "^0.1.15"
pytest = "^8.0.1"
pytest-cov = "^4.1.0"
ruff = "^0.2.2"
mypy = "^1.8.0"
importlib-resources = "^6.1.2"
pytest-parallel = "^0.1.1"
py = "^1.11.0"

[build-system]
requires = ["poetry-core"]
Expand Down
2 changes: 1 addition & 1 deletion pytket-mbqc-py/pytket_mbqc_py/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .qubit_manager import QubitManager
from .graph_circuit import GraphCircuit
from .wasm_file_handler import get_wasm_file_handler
from .cnot_block import CNOTBlocksGraphCircuit
209 changes: 209 additions & 0 deletions pytket-mbqc-py/pytket_mbqc_py/cnot_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
"""Class for generating graph states implementing layers of
CNOT gates.
"""
from .graph_circuit import GraphCircuit
from typing import Tuple, List


class CNOTBlocksGraphCircuit(GraphCircuit):
"""Class for generating graph state implementing
layers of CNOT gates. Consider an n qubit input state
initialised in a computational bases state. A 'layer' of CNOT gates
consists of a CNOT gate acting between qubit 1 and 2, then
another between 2 and 3, etc until a CNOT acts between qubit n-1 and n.
Note that as the inputs are initialised in computational basis states
the CNOT gates can be considered to be classical CNOT gates,
and the ideal outcome is deterministic.
"""

def __init__(
self,
n_physical_qubits: int,
input_state: Tuple[int],
n_layers: int,
) -> None:
"""Initialisation method.
:param n_physical_qubits: The maximum number of physical qubits
available. These qubits will be reused and so the total
number of 'logical' qubits may be larger.
:type n_physical_qubits: int
:param input_state: Integer tuple describing the input
to the circuit. This will be a classical binary
string and so the outcome is deterministic.
:type input_state: Tuple[int]
:param n_layers: The number of layers of CNOT gates.
:type n_layers: int
"""

self.input_state = input_state
self.n_layers = n_layers

# The number of rows of CNOT blocks
# note that this is one less than the number of entries in the
# input state as each CNOT has two inputs.
n_rows = len(input_state) - 1

# This structure contains information about which
# vertices in the graph state corresponds to which
# vertices in each of the CNOT blocks.
# Note that each CNOT block contains 6 vertices implementing
# a CNOT gate. However as some of these may be outputs from
# other CNOT blocks and so there may be repeats.
cnot_block_vertex_list: List[List[List[int]]] = [
[[] for _ in range(n_rows)] for _ in range(n_layers)
]

super().__init__(n_physical_qubits=n_physical_qubits)

for layer in range(n_layers):
# If this is the first layer then the control qubit of the first row needs
# to be initialised. If not then the control vertex is taken from
# the layer before.
if layer == 0:
control_qubit, control_vertex = self.add_input_vertex()
if input_state[0]:
self.X(control_qubit)
else:
control_vertex = cnot_block_vertex_list[layer - 1][0][4]

for row in range(n_rows):
# for each block the 0th qubit is the control.
cnot_block_vertex_list[layer][row].append(control_vertex)

# If this is the 0th layer then the target qubit needs to be
# initialised.
if layer == 0:
target_qubit, target_vertex = self.add_input_vertex()
if input_state[row + 1]:
self.X(target_qubit)
# If this is the last row then the target vertex is the output
# target vertex of the cnot on the same row but previous
# layer.
elif row == n_rows - 1:
target_vertex = cnot_block_vertex_list[layer - 1][row][5]
# Otherwise the target vertex is the output control vertex
# of the CNOT block in the previous layer and the next row.
else:
target_vertex = cnot_block_vertex_list[layer - 1][row + 1][4]
cnot_block_vertex_list[layer][row].append(target_vertex)

# Now the rest of the CNOT block can be constructed.
cnot_block_vertex_list[layer][row].append(self.add_graph_vertex())
self.add_edge(
vertex_one=cnot_block_vertex_list[layer][row][0],
vertex_two=cnot_block_vertex_list[layer][row][2],
)

cnot_block_vertex_list[layer][row].append(self.add_graph_vertex())
self.add_edge(
vertex_one=cnot_block_vertex_list[layer][row][1],
vertex_two=cnot_block_vertex_list[layer][row][3],
)

cnot_block_vertex_list[layer][row].append(self.add_graph_vertex())
self.add_edge(
vertex_one=cnot_block_vertex_list[layer][row][2],
vertex_two=cnot_block_vertex_list[layer][row][4],
)

cnot_block_vertex_list[layer][row].append(self.add_graph_vertex())
self.add_edge(
vertex_one=cnot_block_vertex_list[layer][row][3],
vertex_two=cnot_block_vertex_list[layer][row][5],
)
self.add_edge(
vertex_one=cnot_block_vertex_list[layer][row][3],
vertex_two=cnot_block_vertex_list[layer][row][4],
)

# The control vertex of the next row will be the
# output target vertex of this row.
control_vertex = cnot_block_vertex_list[layer][row][5]

# If this is not the 0th layer then the previous layer
# can be measured.
if layer > 0:
# If this is the 1th later then the inputs of the previous
# layer (the 0th layer) will not have been measured and should now be.
# Note that or other layers they will have been measured by this point
# as they are the 4th and 5th vertices of previous layers.
if layer == 1:
# If this is the 0th row then we need to measure the input
# control. It is not necessary in general as it would be the
# output target of previous blocks.
if row == 0:
self.corrected_measure(
vertex=cnot_block_vertex_list[layer - 1][row][0],
t_multiple=0,
)

self.corrected_measure(
vertex=cnot_block_vertex_list[layer - 1][row][1],
t_multiple=0,
)

# The rest of the block is measured.
self.corrected_measure(
vertex=cnot_block_vertex_list[layer - 1][row][2],
t_multiple=0,
)
self.corrected_measure(
vertex=cnot_block_vertex_list[layer - 1][row][3],
t_multiple=0,
)
self.corrected_measure(
vertex=cnot_block_vertex_list[layer - 1][row][4],
t_multiple=0,
)
self.corrected_measure(
vertex=cnot_block_vertex_list[layer - 1][row][5],
t_multiple=0,
)

# When we reach the end we can measure everything that is not the output.
for row in range(n_rows):
# If there is only 1 layer then the inputs will need to be
# measured. If there are more layers then they will already
# have been measured as inputs to previous later layers.
if n_layers == 1:
self.corrected_measure(
vertex=cnot_block_vertex_list[0][row][0],
t_multiple=0,
)
self.corrected_measure(
vertex=cnot_block_vertex_list[0][row][1],
t_multiple=0,
)

# If there is more than one layer then the output
# qubit in blocks on rows greater then 1 are yet to be measured.
# they are measured on the 0th row as these are output controls
# of previous rows.
elif row > 0:
self.corrected_measure(
vertex=cnot_block_vertex_list[n_layers - 1][row][0],
t_multiple=0,
)

self.corrected_measure(
vertex=cnot_block_vertex_list[n_layers - 1][row][2],
t_multiple=0,
)
self.corrected_measure(
vertex=cnot_block_vertex_list[n_layers - 1][row][3],
t_multiple=0,
)

@property
def output_state(self) -> Tuple[int, ...]:
"""The ideal output bit string.
:return: The ideal output bit string.
:rtype: Tuple[int]
"""
output_state = list(self.input_state)
for _ in range(self.n_layers):
for i in range(len(self.input_state) - 1):
output_state[i + 1] = output_state[i] ^ output_state[i + 1]
return tuple(output_state)
Loading

0 comments on commit 0a2717e

Please sign in to comment.