Skip to content
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
35 changes: 34 additions & 1 deletion src/pgraph/PGraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(
metric: Callable[[NDArray], float] | str | None = None,
heuristic: Callable[[NDArray], float] | str | None = None,
verbose: bool = False,
dim: int | None = None,
):
"""
Abstract base class for graphs
Expand All @@ -39,17 +40,25 @@ def __init__(
:type heuristic: callable or str, optional
:param verbose: print diagnostic information as vertices/edges are
added, defaults to False
:param dim: required length of every vertex's ``coord``, defaults to
None (unconstrained -- vertices may have coordinates of any
length, or none at all)
:type dim: int, optional
:raises ValueError: ``dim`` is given but is not a positive integer

This is the common base class of :class:`UGraph` and :class:`DGraph`
and should not be instantiated directly.

:seealso: :class:`UGraph` :class:`DGraph`
:seealso: :class:`UGraph` :class:`DGraph` :meth:`add_vertex`
"""
if dim is not None and dim <= 0:
raise ValueError(f"dim must be a positive integer, got {dim!r}")
# we use a list and a dict, the list respects the order of adding
self._vertexlist: list[BaseVertex] = []
self._vertexdict: dict[str, BaseVertex] = {}
self._edgelist: set[Edge] = set()
self._verbose = verbose
self._dim = dim
self._ncomponents = 0
self._connectivitychange = False
if metric is None:
Expand Down Expand Up @@ -249,6 +258,8 @@ def add_vertex(
:param name: name of vertex, defaults to "#i"
:type name: str, optional
:raises TypeError: ``coord`` is a ``BaseVertex`` of the wrong kind
:raises ValueError: the graph was constructed with ``dim``, and
``coord`` is given but its length doesn't match
:return: the added vertex
:rtype: BaseVertex subclass

Expand Down Expand Up @@ -276,6 +287,18 @@ def add_vertex(
>>> v2 = g.add_vertex(UVertex(coord=[1,1], name='v2'))
>>> print(v2.name)

If the graph was constructed with a required ``dim`` (see
:meth:`_BaseGraph.__init__`), every embedded vertex must have a
coordinate of exactly that length -- adding one of the wrong length
raises ``ValueError``:

.. runblock:: pycon

>>> from pgraph import UGraph
>>> g = UGraph(dim=6)
>>> v1 = g.add_vertex(coord=[0, 0, 0, 0, 0, 0], name='pose1')
>>> print(v1)

:seealso: :meth:`vertex_copy`
"""
if isinstance(coord, self._vertex_cls):
Expand All @@ -288,6 +311,16 @@ def add_vertex(
else:
vertex = self._vertex_cls(coord, name=name)

if (
self._dim is not None
and vertex.coord is not None
and len(vertex.coord) != self._dim
):
raise ValueError(
f"vertex coord has length {len(vertex.coord)}, "
f"but this graph requires dim={self._dim}"
)

if name is None:
name = vertex.name
if name is None:
Expand Down
23 changes: 23 additions & 0 deletions tests/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,29 @@ def test_add_vertex(self):
self.assertTrue(v in g)
self.assertTrue(v._graph, g)

def test_dim(self):

# unconstrained by default: any length, or no coord at all
g = UGraph()
g.add_vertex(coord=[1, 2])
g.add_vertex(coord=[1, 2, 3, 4])
g.add_vertex()
self.assertEqual(g.n, 3)

# dim enforces every embedded vertex has exactly that length
g = UGraph(dim=6)
g.add_vertex(coord=[0, 0, 0, 0, 0, 0], name='pose1')
g.add_vertex(name='untyped') # no coord: not checked
with self.assertRaises(ValueError):
g.add_vertex(coord=[1, 2, 3], name='bad')
self.assertEqual(g.n, 2)

# dim must be a positive integer
with self.assertRaises(ValueError):
UGraph(dim=0)
with self.assertRaises(ValueError):
UGraph(dim=-1)

def test_properties(self):

g = UGraph()
Expand Down
Loading