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

[FTQC] Add Lattice class to ftqc module #6958

Merged
merged 49 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
8ce73cd
initial commit
multiphaseCFD Feb 12, 2025
3d7387c
use grid_graph API to generate 1,2,3D lattice structure
multiphaseCFD Feb 13, 2025
6ba944a
remove print
multiphaseCFD Feb 13, 2025
00de819
drop labelling and draw support
multiphaseCFD Feb 14, 2025
a2d4050
add lattice generation support
multiphaseCFD Feb 18, 2025
ce8706b
add unit tests
multiphaseCFD Feb 18, 2025
d07ddcd
remove graphiz dependency
multiphaseCFD Feb 18, 2025
431f158
tidy up
multiphaseCFD Feb 18, 2025
799faba
make format
multiphaseCFD Feb 18, 2025
24aeb5c
Merge branch 'master' into ftqc_cluster_state_test
multiphaseCFD Feb 18, 2025
25e74f6
update docstr
multiphaseCFD Feb 18, 2025
11475b8
add attribute setter
multiphaseCFD Feb 18, 2025
cded466
add changelog
multiphaseCFD Feb 18, 2025
506947b
update format
multiphaseCFD Feb 18, 2025
25ac1cc
add docstr to unit tests
multiphaseCFD Feb 18, 2025
0f3bfa2
disables the inconsistent-return-statements check
multiphaseCFD Feb 18, 2025
218fd1f
make format
multiphaseCFD Feb 18, 2025
e18978f
fix codefactor warning
multiphaseCFD Feb 18, 2025
5ff1920
tidy up code
multiphaseCFD Feb 18, 2025
c2300a0
make format
multiphaseCFD Feb 18, 2025
c4d6881
update docstr
multiphaseCFD Feb 18, 2025
58748fc
Merge branch 'master' into ftqc_cluster_state_test
multiphaseCFD Feb 18, 2025
fa89133
fix code factor issues
multiphaseCFD Feb 18, 2025
6bfef48
update py unit test file name
multiphaseCFD Feb 18, 2025
8306156
fix pylint issues
multiphaseCFD Feb 18, 2025
98d0c9c
Update doc/releases/changelog-dev.md
multiphaseCFD Feb 19, 2025
668da34
apply some suggestions
multiphaseCFD Feb 20, 2025
c86e142
remove pylint
multiphaseCFD Feb 24, 2025
0df3271
apply some suggestions
multiphaseCFD Feb 24, 2025
6430837
Merge branch 'master' into ftqc_cluster_state_test
multiphaseCFD Feb 24, 2025
f3ad91b
update typo
multiphaseCFD Feb 24, 2025
11adf72
update codes
multiphaseCFD Feb 24, 2025
45576f9
update LatticeShape class and map
multiphaseCFD Feb 26, 2025
59000f5
make format
multiphaseCFD Feb 26, 2025
bcb6fc7
apply some changes
multiphaseCFD Feb 28, 2025
b276afc
apply more suggestions
multiphaseCFD Feb 28, 2025
6199833
Merge branch 'master' into ftqc_cluster_state_test
multiphaseCFD Feb 28, 2025
3c94961
fix codecov warning
multiphaseCFD Feb 28, 2025
cb1988a
udpate changelog
multiphaseCFD Feb 28, 2025
f690dbe
Merge branch 'master' into ftqc_cluster_state_test
multiphaseCFD Feb 28, 2025
0d88a3f
Merge branch 'master' into ftqc_cluster_state_test
multiphaseCFD Feb 28, 2025
d4e2f0e
update property method
multiphaseCFD Feb 28, 2025
e2cea57
Merge branch 'master' into ftqc_cluster_state_test
multiphaseCFD Feb 28, 2025
d450a0d
Merge branch 'master' into ftqc_cluster_state_test
multiphaseCFD Feb 28, 2025
87981e0
fix codecov complains
multiphaseCFD Feb 28, 2025
74e14fb
fix typo
multiphaseCFD Feb 28, 2025
bf558b0
Merge branch 'master' into ftqc_cluster_state_test
multiphaseCFD Feb 28, 2025
349245e
Update tests/ftqc/test_ftqc_lattice.py
multiphaseCFD Feb 28, 2025
49c6a97
Update tests/ftqc/test_ftqc_lattice.py
multiphaseCFD Feb 28, 2025
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
4 changes: 4 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
* `qml.SWAP` now has sparse representation.
[(#6965)](https://github.com/PennyLaneAI/pennylane/pull/6965)

* A `Lattice` class and a `generate_lattice` method is added to the `qml.ftqc` module. The `generate_lattice` method is to generate 1D, 2D, 3D grid graphs with the given geometric parameters.
[(#6958)](https://github.com/PennyLaneAI/pennylane/pull/6958)

* `qml.QubitUnitary` now accepts sparse CSR matrices (from `scipy.sparse`). This allows efficient representation of large unitaries with mostly zero entries. Note that sparse unitaries are still in early development and may not support all features of their dense counterparts.
[(#6889)](https://github.com/PennyLaneAI/pennylane/pull/6889)

Expand Down Expand Up @@ -485,4 +488,5 @@ Korbinian Kottmann,
Christina Lee,
Mudit Pandey,
Andrija Paurevic,
Shuli Shu,
David Wierichs
3 changes: 3 additions & 0 deletions pennylane/ftqc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@
"""
from pennylane import ExperimentalWarning
from .parametric_midmeasure import ParametricMidMeasureMP, diagonalize_mcms
from .lattice import Lattice, generate_lattice

__all__ = [
"Lattice",
"ParametricMidMeasureMP",
"diagonalize_mcms",
"generate_lattice",
]
169 changes: 169 additions & 0 deletions pennylane/ftqc/lattice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Copyright 2025 Xanadu Quantum Technologies Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=protected-access, inconsistent-return-statements
"""
This file defines classes and functions for creating lattice objects that store topological
connectivity information.
"""

from collections.abc import Sequence
from enum import Enum, auto
from functools import lru_cache

import networkx as nx


class Lattice:
"""Represents a qubit lattice structure.

This Lattice class, inspired by the design of :class:`~pennylane.spin.Lattice`, leverages `NetworkX` to represent the relationships within the lattice structure.

Args:
lattice_shape: Name of the lattice shape.
graph (nx.Graph): A NetworkX undirected graph object. If provided, `nodes` and `edges` are ignored.
nodes (List): Nodes to construct a graph object. Ignored if `graph` is provided.
edges (List): Edges to construct the graph. Ignored if `graph` is provided.
Raises:
ValueError: If neither `graph` nor both `nodes` and `edges` are provided.
"""

# TODOs: To support braiding operations, Lattice should support nodes/edges addition/deletion.

def __init__(
self, lattice_shape: str, graph: nx.Graph = None, nodes: list = None, edges: list = None
):
self._lattice_shape = lattice_shape
if graph is None:
if nodes is None and edges is None:
raise ValueError(
"Neither a networkx Graph object nor nodes together with edges are provided."
)
self._graph = nx.Graph()
self._graph.add_nodes_from(nodes)
self._graph.add_edges_from(edges)
else:
self._graph = graph

@property
def shape(self) -> str:
r"""Returns the lattice shape name."""
return self._lattice_shape

def get_neighbors(self, node):
r"""Returns the neighbors of a given node in the lattice.

Args:
node: a target node label.
"""
return self._graph.neighbors(node)

@property
def get_nodes(self):
r"""Returns all nodes in the lattice."""
return self._graph.nodes

@property
def get_edges(self):
r"""Returns all edges in the lattice."""
return self._graph.edges

@property
def get_graph(self) -> nx.Graph:
r"""Returns the underlying NetworkX graph object representing the lattice."""
return self._graph


class LatticeShape(Enum):
"""Enum to define valid set of lattice shape supported."""

chain = auto()
square = auto()
rectangle = auto()
triangle = auto()
honeycomb = auto()
cubic = auto()


# map between lattice name and dimensions
_LATTICE_DIM_MAP = {
"chain": 1,
"square": 2,
"rectangle": 2,
"cubic": 3,
"triangle": 2,
"honeycomb": 2,
}

# map between lattice name and networkx method name
_LATTICE_GENERATOR_MAP = {
"chain": "grid_graph",
"square": "grid_graph",
"rectangle": "grid_graph",
"cubic": "grid_graph",
"triangle": "triangular_lattice_graph",
"honeycomb": "hexagonal_lattice_graph",
}


@lru_cache
def _supported_shapes():
r"""Return the supported shape in str"""
return [shape.name for shape in LatticeShape]


def generate_lattice(dims: Sequence[int], lattice: str) -> Lattice:
r"""Generates a :class:`~pennylane.ftqc.Lattice` object with a given geometric parameters and its shape name.

Args:
dims(List[int]): Geometric parameters for lattice generation. For lattices generated by `nx.grid_graph` ( ``'chain'``, ``'rectangle'``, ``'square'``, ``'cubic'``),
`dims` contains the number of nodes in the each direction of grid. Per ``'honeycomb'`` or ``'triangle'``, the generated lattices will have dims[0] rows and dims[1]
columns of hexagons or triangles.
lattice (str): Shape of the lattice. Input values can be ``'chain'``, ``'square'``, ``'rectangle'``, ``'honeycomb'``, ``'triangle'``, ``'cubic'``.

Returns:
a :class:`~pennylane.ftqc.Lattice` object.

Raises:
ValueError: If the lattice shape is not supported or the dimensions are invalid.
"""

lattice_shape = lattice.strip().lower()
supported_shapes = _supported_shapes()

if lattice_shape not in supported_shapes:
raise ValueError(
f"Lattice shape, '{lattice}' is not supported."
f"Please set lattice to: {supported_shapes}."
)

if _LATTICE_DIM_MAP[lattice_shape] != len(dims):
raise ValueError(
f"For a {lattice_shape} lattice, the length of dims should be {_LATTICE_DIM_MAP[lattice_shape]} instead of {len(dims)}"
)

lattice_generate_method = getattr(nx, _LATTICE_GENERATOR_MAP[lattice_shape])

if _LATTICE_GENERATOR_MAP[lattice_shape] == "grid_graph":
lattice_obj = Lattice(lattice_shape, lattice_generate_method(dims))
return lattice_obj

if _LATTICE_GENERATOR_MAP[lattice_shape] in [
"triangular_lattice_graph",
"hexagonal_lattice_graph",
]:
lattice_obj = Lattice(lattice_shape, lattice_generate_method(dims[0], dims[1]))
return lattice_obj

raise ValueError(

Check warning on line 167 in pennylane/ftqc/lattice.py

View check run for this annotation

Codecov / codecov/patch

pennylane/ftqc/lattice.py#L167

Added line #L167 was not covered by tests
f"The given {lattice_shape} lattice shape and geometric parameters {dims} are not supported."
) # pragma : no cover
129 changes: 129 additions & 0 deletions tests/ftqc/test_ftqc_lattice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Copyright 2025 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=no-name-in-module, no-self-use, protected-access
"""Unit tests for the lattice module"""

import networkx as nx
import pytest

from pennylane.ftqc import Lattice, generate_lattice


class TestLattice:
"""Test for the qml.ftqc.Lattice class."""

def test_lattice_creation_with_graph(self):
"""Test for Lattice object created by a nx.Graph object."""
graph = nx.Graph([(0, 1), (1, 2)])
lattice = Lattice("test", graph=graph)
assert lattice.shape == "test"
assert len(lattice.get_nodes()) == 3
assert len(lattice.get_edges()) == 2

def test_lattice_creation_with_nodes_and_edges(self):
"""Test for Lattice object created by a list of nodes and edges."""
nodes = [0, 1, 2]
edges = [(0, 1), (1, 2)]
lattice = Lattice("test", nodes=nodes, edges=edges)
assert len(lattice.get_nodes()) == 3
assert len(lattice.get_edges()) == 2

def test_lattice_creation_invalid(self):
"""Test for Lattice object created with lattice name only. ValueError will be raised as neither graph nor nodes/edges provided"""
with pytest.raises(ValueError, match="Neither a networkx Graph object nor nodes together"):
Lattice("test")

def test_get_lattice_shape(self):
"""Test for get_lattice_shape()."""
lattice = Lattice("test_shape", nx.Graph())
assert lattice.shape == "test_shape"

def test_get_neighbors(self):
"""Test for getting the neighbors of a node."""
graph = nx.grid_graph([3, 3])
lattice = Lattice("rectangle", graph)
assert set(lattice.get_neighbors((1, 1))) == set(graph.neighbors((1, 1)))

def test_get_nodes(self):
"""Test for getting nodes."""
nodes = [0, 1, 2]
edges = [(0, 1), (1, 2)]
lattice = Lattice("test", nodes=nodes, edges=edges)
assert set(lattice.get_nodes) == set(nodes)

def test_get_edges(self):
"""Test for getting edges."""
edges = [(0, 1), (1, 2)]
lattice = Lattice("test", nodes=[0, 1, 2], edges=edges)
assert set(lattice.get_edges) == set(edges)

def test_get_graph(self):
"""Test for getting graph."""
graph = nx.Graph([(0, 1), (1, 2)])
lattice = Lattice("test", graph=graph)
assert lattice.get_graph is graph


class TestGenerateLattice:
"""Test for generate_lattice method."""

def test_generate_chain_lattice(self):
"""Test to generate a 1D chain lattice."""
lattice = generate_lattice([5], "chain")
assert isinstance(lattice, Lattice)
assert len(lattice.get_nodes()) == 5

def test_generate_rectangle_lattice(self):
"""Test to generate a 2D rectangle lattice."""
lattice = generate_lattice([3, 4], "rectangle")
assert isinstance(lattice, Lattice)
assert len(lattice.get_nodes()) == 12

def test_generate_cubic_lattice(self):
"""Test to generate a 3D cubic lattice."""
lattice = generate_lattice([2, 2, 2], "cubic")
assert isinstance(lattice, Lattice)
assert len(lattice.get_nodes()) == 8

def test_generate_triangle_lattice(self):
"""Test to generate a 2D triangle lattice."""
lattice = generate_lattice([3, 4], "triangle")
assert isinstance(lattice, Lattice)
assert len(lattice.get_nodes) == 12

def test_generate_honeycomb_lattice(self):
"""Test to generate a 2D honeycomb lattice."""
lattice = generate_lattice([1, 4], "honeycomb")
assert isinstance(lattice, Lattice)
assert len(lattice.get_nodes()) == 18

def test_generate_invalid_lattice_shape(self):
"""Test for an unsupported lattice shape."""
with pytest.raises(ValueError):
generate_lattice([2, 2], "invalid_shape")

@pytest.mark.parametrize(
"dims, shape",
[
([2, 2], "chain"),
([2], "rectangle"),
([2, 2], "cubic"),
([2, 2, 2], "triangle"),
([2], "honeycomb"),
],
)
def test_generate_invalid_dimensions(self, dims, shape):
"""Test for an incorrect dims input."""
with pytest.raises(ValueError, match="the length of dims should be"):
generate_lattice(dims, shape)