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

Initialize Lattices from networkx.Graphs #467

Closed
wants to merge 6 commits into from
Closed
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
2 changes: 2 additions & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ morse
mpl
mul
multi
multigraph
multinomial
mypy
namelist
Expand All @@ -275,6 +276,7 @@ natom
nd
ndarray
nelec
networkx
neuropeptide
nicholas
nielsen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,22 @@
# that they have been altered from the originals.

"""General Lattice."""

from copy import deepcopy
from dataclasses import asdict, dataclass
from typing import Callable, List, Optional, Sequence, Tuple, Union
import numbers

import numpy as np
from retworkx import NodeIndices, PyGraph, WeightedEdgeList, adjacency_matrix

try:
import networkx as nx

HAS_NX = True
except ImportError:
HAS_NX = False

from retworkx import NodeIndices, PyGraph, WeightedEdgeList, adjacency_matrix, networkx_converter
from retworkx.visualization import mpl_draw

from qiskit.exceptions import MissingOptionalLibraryError
Expand Down Expand Up @@ -107,23 +117,35 @@ class LatticeDrawStyle:
class Lattice:
"""General Lattice."""

def __init__(self, graph: PyGraph) -> None:
def __init__(self, graph: Union[PyGraph, nx.Graph]) -> None:
"""
Args:
graph: Input graph for Lattice. `graph.multigraph` must be False.
graph: Input graph for Lattice. Can be provided as ``retworkx.PyGraph``, which is
used internally, or, for convenience, as ``networkx.Graph``. The graph
cannot be a multigraph.

Raises:
ValueError: If `graph.multigraph` is True for a given graph, it is invalid.
ValueError: If the input graph is a multigraph.
ValueError: If the graph edges are non-numeric.
"""
if HAS_NX and isinstance(graph, nx.Graph):
graph = networkx_converter(graph)

if graph.multigraph:
raise ValueError(
f"Invalid `graph.multigraph` {graph.multigraph} is given. "
"`graph.multigraph` must be `False`."
)
if graph.edges() == [None] * graph.num_edges():
weighted_edges = [edge + (1.0,) for edge in graph.edge_list()]
for start, end, weight in weighted_edges:
graph.update_edge(start, end, weight)

# validate the edge weights
for edge_index, edge in graph.edge_index_map().items():
weight = edge[2]
if weight is None or weight == {}:
# None or {} is updated to be 1
graph.update_edge_by_index(edge_index, 1)
elif not isinstance(weight, numbers.Number):
raise ValueError(f"Unsupported weight {weight} on edge with index {edge_index}.")

self._graph = graph

self.pos: Optional[dict] = None
Expand Down
17 changes: 17 additions & 0 deletions releasenotes/notes/lattice-from-networkx-20c7c8119af77f36.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
features:
Cryoris marked this conversation as resolved.
Show resolved Hide resolved
- |
Add the option to initialize a :class:`~qiskit_nature.problems.second_quantization.lattice.Lattice`
from a ``networkx.Graph`` object, which will be internally converted to a ``retworkx.PyGraph``
for performance.

For example, you can now construct a lattice as

.. code-block:: python

import networkx as nx
from qiskit_nature.problems.second_quantization.lattice import Lattice

# 3-regular random graph on 6 nodes
graph = nx.generators.random_graphs.random_regular_graph(3, n=6)
lattice = Lattice(graph)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import numpy as np
from numpy.testing import assert_array_equal
import networkx as nx
from retworkx import PyGraph, is_isomorphic

from qiskit_nature.problems.second_quantization.lattice import Lattice
Expand Down Expand Up @@ -133,3 +134,44 @@ def test_to_adjacency_matrix(self):

target_matrix = np.array([[0, 1, 1], [1, 0, 0], [1, 0, 1]])
assert_array_equal(lattice.to_adjacency_matrix(), target_matrix)

def test_from_networkx(self):
"""Test initialization from a networkx graph."""
graph = nx.Graph()
graph.add_nodes_from(range(5))
graph.add_edges_from([(i, i + 1) for i in range(4)])
lattice = Lattice(graph)

target_graph = PyGraph()
target_graph.add_nodes_from(range(5))
target_graph.add_edges_from([(i, i + 1, 1) for i in range(4)])

self.assertTrue(
is_isomorphic(lattice.graph, target_graph, edge_matcher=lambda x, y: x == y)
)

def test_nonnumeric_weight_raises(self):
"""Test the initialization with a graph with non-numeric edge weights raises."""
graph = PyGraph(multigraph=False)
graph.add_nodes_from(range(3))
graph.add_edges_from([(0, 1, 1), (1, 2, "banana")])

with self.assertRaises(ValueError):
_ = Lattice(graph)

def test_edges_removed(self):
"""Test the initialization with a graph where edges have been removed."""
graph = PyGraph(multigraph=False)
graph.add_nodes_from(range(3))
graph.add_edges_from([(0, 1, 1), (1, 2, 1)])
graph.remove_edge_from_index(0)

lattice = Lattice(graph)

target_graph = PyGraph(multigraph=False)
target_graph.add_nodes_from(range(3))
target_graph.add_edges_from([(1, 2, 1)])

self.assertTrue(
is_isomorphic(lattice.graph, target_graph, edge_matcher=lambda x, y: x == y)
)