Graph analysis
Querying structure – counts, adjacent vertices, incident edges, degree, connectivity, distance – for directed and undirected graphs. See Path planning for finding paths between vertices, and Transformations to matrix representations for the matrix and dot forms of the whole graph.
A vertex is queried in two complementary ways: by the vertices it is
adjacent to, and by the edges incident on it. For the adjacent vertices
adjacent() is the canonical name; for a directed
graph successors() is the same list under its
directed name, and predecessors() is the vertices
with an edge to the vertex. The edges are
out_edges() and
in_edges(), and
incidences() pairs each adjacent vertex with the
edge that leads to it. edgeto() is the edge to
one adjacent vertex, the cheapest if there are parallel edges, and
edges_to() lists all of them.
Graph
The size and kind of the graph
Number of vertices |
|
Number of edges |
|
Number of components |
|
Test if the graph is directed |
|
All edges in the graph, in the order they were added |
Structure and connectivity
Test if graph is cyclic |
|
All vertices in specified graph component |
|
Test if vertices belong to same graph component |
|
Graph connectivity |
|
Average degree of the graph |
Geometry and consistency
For a graph whose vertices have coordinates,
coordinates() is the matrix of them, one column per
vertex. closest() finds the nearest vertex to a
point, near() the vertices within a distance of a
point and within() the vertices inside a box. Passing
the result of the last to subgraph() gives the part of
the graph in a region, without the edges that cross its border, and a boolean
mask computed from the coordinates selects in the same way.
These are a single pass over the vertices. For a large graph that is queried
often, build_kdtree() builds an index that
closest() and near() then
use, which needs scipy and is for the built-in metrics.
Coordinates of all the vertices as a matrix |
|
Vertex closest to point |
|
Vertices within a distance of a point |
|
Vertices inside a box |
|
Build a spatial index of the vertices, used by |
|
Subgraph induced by a set of vertices |
|
Check that the graph's records agree with each other |
The matrices of a graph (adjacency(),
degree(), incidence(),
laplacian(), distance())
are in Transformations to matrix representations.
Vertex
Identity and position
Name of the vertex |
|
Number of the graph component that contains the vertex |
|
The x-coordinate of an embedded vertex |
|
The y-coordinate of an embedded vertex |
|
The z-coordinate of an embedded vertex |
Adjacent vertices
Vertices adjacent to this vertex |
|
Test if a vertex is adjacent to this vertex |
|
Vertices that this vertex has an edge to |
|
Vertices that have an edge to this vertex |
Note
neighbours() and neighbors() are the same method as
adjacent(), and isneighbour() and
isneighbor() the same as isadjacent().
They are spelling variants, not deprecated.
Incident edges
The edges at the vertex, in the order they were connected |
|
The edges leaving the vertex |
|
The edges entering the vertex |
|
Adjacent vertices paired with the edges that lead to them |
|
The edge from this vertex to an adjacent vertex |
|
All the edges from this vertex to another vertex |
Degree
Degree of vertex |
|
Number of edges entering the vertex |
|
Number of edges leaving the vertex |
Distance
Distance from vertex to point |
|
Heuristic distance to another vertex |
|
Return the closest vertex to this vertex |
Edge
An Edge is described in full on Edges & Vertices. The
attributes and methods used to query one are
First vertex of the edge, the start vertex for a directed edge |
|
Second vertex of the edge, the end vertex for a directed edge |
|
The two vertices of this edge |
|
Return other end of an edge |
and every edge also has cost and data attributes.
Details
- property Graph.n: int
Number of vertices
- Returns:
Number of vertices
- Return type:
>>> from pgraph import UGraph >>> g = UGraph() >>> g.add_vertex(name='v1') Vertex[v1, coord=(?)] >>> g.add_vertex(name='v2') Vertex[v2, coord=(?)] >>> print(g.n) 2
- property Graph.ne: int
Number of edges
- Returns:
Number of vertices
- Return type:
>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(name='v1') >>> v2 = g.add_vertex(name='v2') >>> g.add_edge(v1, v2) Edge{[v1] -- [v2], cost=None} >>> print(g.ne) 1
- property Graph.nc: int
Number of components
- Returns:
Number of components
- Return type:
Number of components in the graph. Components are labeled from 0 to
g.nc-1.For a directed graph the components are weakly connected: the direction of the edges is ignored, so two vertices are in the same component if there is a path between them that may run against the arrows. A graph with no vertices has no components.
Changed in version 1.0: Does not hang. For many directed graphs, the smallest being the 4-cycle, the algorithm of 0.6.5 never finished, which also stopped
component(),samecomponent()andplot().>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(name='v1') >>> v2 = g.add_vertex(name='v2') >>> v3 = g.add_vertex(name='v3') >>> g.add_edge(v1, v2) Edge{[v1] -- [v2], cost=None} >>> print(g.nc) 2
Note
A lazy approach is used, and if a connectivity changing operation has been performed since the last call, the graph coloring algorithm is run which is potentially expensive for a large graph.
- Seealso:
- property Graph.directed: bool
Test if the graph is directed
An undirected edge is recorded at both of its vertices, and is followed in either direction. A directed edge is recorded as leaving its start vertex and entering its end vertex.
- Graph.edges()[source]
All edges in the graph, in the order they were added
- Returns:
All edges in the graph
- Return type:
list of Edge references
The edges are in the order they were added to the graph. This is a new list, so it can be modified or sorted without affecting the graph, and edges can be removed from the graph while iterating over it.
We can iterate over all edges in the graph by:
for e in g.edges(): print(e)
and select one by index, for example the second edge added is
g.edges()[1].Note
Like
Vertex.edges()this is alist, but where that lists the edges attached to one vertex, this lists every edge in the graph once. A self-loop is one edge here, but appears twice in theVertex.edges()of an undirected vertex because it has two ends there.Changed in version 1.0: Returns a
listin order of addition. In 0.6.5 this was the graph’s ownset, with no defined order, although the documentation saidlist.>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[1,1], name='v2') >>> g.add_edge(v1, v2) Edge{[v1] -- [v2], cost=1.414} >>> print(g.edges()) [Edge{[v1] -- [v2], cost=1.414}]
- Seealso:
- Graph.iscyclic()[source]
Test if graph is cyclic
- Returns:
true if the graph contains at least one cycle
- Return type:
For an undirected graph this is a simple count: a forest (acyclic) has exactly
n - ncedges, one per component-connecting edge with no redundancy, so any excess indicates a cycle. This is O(1) givenn(),ne()andnc(), which are already cached.For a directed graph this runs Kahn’s algorithm: repeatedly remove vertices with no remaining incoming edges. If every vertex can eventually be removed, the graph is a DAG (acyclic); if some are never freed, they form a cycle. This is O(V+E), using vertices’ outgoing edges via
Vertex.adjacent()the same waypath_BFS()and friends do.>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(name='v1') >>> v2 = g.add_vertex(name='v2') >>> v3 = g.add_vertex(name='v3') >>> g.add_edge(v1, v2) Edge{[v1] -- [v2], cost=None} >>> g.add_edge(v2, v3) Edge{[v2] -- [v3], cost=None} >>> print(g.iscyclic()) False >>> g.add_edge(v3, v1) Edge{[v3] -- [v1], cost=None} >>> print(g.iscyclic()) True
Note
A self-loop is a cycle of length one, so it makes either kind of graph cyclic. Two parallel edges between the same pair of vertices are a cycle in an undirected graph, but a repeated arrow in a directed graph is not.
Note
A matrix-based test also exists in theory – a digraph is acyclic iff its adjacency matrix is nilpotent (all eigenvalues zero) – but that needs an O(N^3) eigendecomposition plus a floating-point zero-tolerance judgement call, to answer a question Kahn’s algorithm answers exactly with integers in O(V+E). Not used here for that reason.
- Seealso:
- Graph.component(c)[source]
All vertices in specified graph component
- Parameters:
c (
int) – component index- Returns:
vertices belonging to component
c- Return type:
list of Vertex subclass
graph.component(c)is a list of all vertices in graph componentc.>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(name='v1') >>> v2 = g.add_vertex(name='v2') >>> v3 = g.add_vertex(name='v3') >>> g.add_edge(v1, v2); Edge{[v1] -- [v2], cost=None} >>> print(g) UGraph: 3 vertices, 1 edge, 2 components >>> print(g.component(0)) [Vertex[v1, coord=(?)], Vertex[v2, coord=(?)]]
- Seealso:
- Graph.samecomponent(v1, v2)[source]
Test if vertices belong to same graph component
- Parameters:
v1 (Vertex subclass) – vertex
v2 (Vertex subclass) – vertex
- Returns:
true if vertices belong to same graph component
- Return type:
Test whether vertices belong to the same component. A component is a set of vertices that are connected by a path of edges, ignoring their direction, so for a directed graph this does not mean that there is a path from one to the other:
a -> c <- bis one component, but there is no path fromatob.>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(name='v1') >>> v2 = g.add_vertex(name='v2') >>> v3 = g.add_vertex(name='v3') >>> g.add_edge(v1, v2) Edge{[v1] -- [v2], cost=None} >>> print(g.samecomponent(v1, v2)) True >>> print(g.samecomponent(v1, v3)) False
- Seealso:
- Graph.connectivity(vertices=None)[source]
Graph connectivity
- Parameters:
vertices (iterable of Vertex subclass, optional) – vertices to report connectivity for, defaults to all vertices in the graph
- Returns:
a list with the number of edges per vertex
- Return type:
This is the
Vertex.degreeof each vertex: the number of edge ends at it, which is the number of edges connected to it except that a self-loop has two ends and so counts twice. For a directed graph it counts the edges entering the vertex as well as those leaving.Changed in version 1.0: For a
DGraphthis counted only the edges leaving each vertex. Use[v.out_degree for v in g]for the previous values.The average vertex connectivity is:
mean(g.connectivity())
and the minimum vertex connectivity is:
min(g.connectivity())
>>> from pgraph import UGraph >>> import numpy as np >>> g = UGraph() >>> for i in range(5): ... g.add_vertex(np.random.rand(2)); Vertex[#0, coord=(0.744, 0.7207)] Vertex[#1, coord=(0.5883, 0.3748)] Vertex[#2, coord=(0.09699, 0.4609)] Vertex[#3, coord=(0.6158, 0.2589)] Vertex[#4, coord=(0.02597, 0.001061)] >>> for i, j in [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (3, 4)]: ... g.add_edge(g[i], g[j]); Edge{[#0] -- [#1], cost=0.3793} Edge{[#0] -- [#2], cost=0.6972} Edge{[#0] -- [#3], cost=0.4793} Edge{[#1] -- [#2], cost=0.4988} Edge{[#1] -- [#3], cost=0.1192} Edge{[#3] -- [#4], cost=0.6437} >>> c = g.connectivity() >>> print(c) [3, 3, 2, 3, 1]
Note
This is the diagonal of
degree(). Parallel edges are counted individually.- Seealso:
- Graph.average_degree()[source]
Average degree of the graph
- Returns:
average degree
- Return type:
Average degree is \(2 E / N\) where \(E\) is the total number of edges and \(N\) is the number of vertices, for an undirected or a directed graph. It is the mean of
Vertex.degree, and every edge has two ends, so the degrees sum to \(2 E\).Parallel edges and self-loops are each counted as one edge in \(E\); a self-loop contributes 2 to the degree sum.
Changed in version 1.0: For a
DGraphthis was \(E / N\), the mean out-degree (which equals the mean in-degree). It is now \(2 E / N\), the mean of the degree, which is twice the old value. Useg.ne / g.nfor the previous number.>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(name='v1') >>> v2 = g.add_vertex(name='v2') >>> v3 = g.add_vertex(name='v3') >>> g.add_edge(v1, v2); Edge{[v1] -- [v2], cost=None} >>> g.add_edge(v2, v3); Edge{[v2] -- [v3], cost=None} >>> print(g.average_degree()) 1.3333333333333333
- Seealso:
- Graph.coordinates()[source]
Coordinates of all the vertices as a matrix
- Returns:
coordinates, one column per vertex in the order they were added
- Return type:
ndarray(dim, n)
- Raises:
ValueError – the coordinates of two vertices differ in length
Column
iis the coordinate ofg[i], the same order as the rows and columns ofadjacency(). A vertex that has no coordinate has a column of NaN, so the matrix always has one column per vertex. The number of rows isdimif the graph was given one, otherwise the length of the first coordinate, and 0 if no vertex has one. Elements are float.The result is a view of a new array that has one row per vertex, so the coordinate of each vertex is contiguous in memory and
C[:, i]is a contiguous slice, while a row such asC[0]is strided. Usenumpy.ascontiguousarray()if you need the rows to be contiguous.Note
This is a snapshot, made each time: changing it does not move the vertices, and moving a vertex does not change a matrix that was already returned.
With NumPy this selects vertices in a single operation, as
within()andnear()do, or it can be given to a spatial index of your own.>>> from pgraph import UGraph >>> g = UGraph() >>> for i in range(3): ... g.add_vertex(coord=[i, 10 * i]) Vertex[#0, coord=(0, 0)] Vertex[#1, coord=(1, 10)] Vertex[#2, coord=(2, 20)] >>> g.coordinates() array([[ 0., 1., 2.], [ 0., 10., 20.]])
- Seealso:
- Graph.closest(coord)[source]
Vertex closest to point
- Parameters:
coord (ndarray(n)) – coordinates of a point
- Returns:
closest vertex and its distance, or
(None, inf)if no vertex in the graph has a coordinate- Return type:
Vertex subclass or None, float
Returns the vertex closest to the given point. Distance is computed according to the graph’s metric. Vertices without a coordinate (
coordis None) are skipped – they have no position to compare.This is a pass over the vertices, unless a spatial index has been built with
build_kdtree()and is still valid.>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[10,10], name='v2') >>> vertex, d = g.closest([1, 1]) >>> print(vertex, d) [v1] 1.4142135623730951
- Seealso:
- Graph.near(coord, radius)[source]
Vertices within a distance of a point
- Parameters:
- Raises:
ValueError –
radiusis negative- Returns:
the vertices within
radiusof the point with their distances, nearest first- Return type:
Distance is computed according to the graph’s metric, as it is by
closest(), of which this is the generalization to every vertex within a radius. Vertices without a coordinate are skipped, and vertices at equal distance are in the order of the graph.For the built-in metrics the distance to every vertex is found in one operation on
coordinates(), or by the spatial index ifbuild_kdtree()was called; a metric of your own is applied to each vertex in turn.>>> from pgraph import UGraph >>> g = UGraph() >>> for i in range(5): ... g.add_vertex(coord=[i, 0], name=f"v{i}") Vertex[v0, coord=(0, 0)] Vertex[v1, coord=(1, 0)] Vertex[v2, coord=(2, 0)] Vertex[v3, coord=(3, 0)] Vertex[v4, coord=(4, 0)] >>> [(v.name, d) for v, d in g.near([2.2, 0], 1.5)] [('v2', 0.20000000000000018), ('v3', 0.7999999999999998), ('v1', 1.2000000000000002)]
- Seealso:
- Graph.within(box)[source]
Vertices inside a box
- Parameters:
box (ndarray(2,n)) – lower and upper corners of the box
- Raises:
ValueError –
boxis not 2 x n, its upper corner is below its lower corner in some dimension, or the coordinates of the vertices do not have n elements- Returns:
the vertices inside the box, in the order of the graph
- Return type:
list of Vertex subclass
The first row of
boxis the lower corner, the second row the upper corner, so in two dimensions it is[[xmin, ymin], [xmax, ymax]]. The box is closed: a vertex on its boundary is inside. Vertices without a coordinate are skipped, as they are byclosest().The test is made for all the vertices at once on
coordinates(). Together withsubgraph()it selects the part of an embedded graph in a region>>> from pgraph import UGraph >>> g = UGraph() >>> for i in range(5): ... g.add_vertex(coord=[i, i], name=f"v{i}") Vertex[v0, coord=(0, 0)] Vertex[v1, coord=(1, 1)] Vertex[v2, coord=(2, 2)] Vertex[v3, coord=(3, 3)] Vertex[v4, coord=(4, 4)] >>> g.within([[1, 1], [3, 3]]) [Vertex[v1, coord=(1, 1)], Vertex[v2, coord=(2, 2)], Vertex[v3, coord=(3, 3)]] >>> g.subgraph(g.within([[1, 1], [3, 3]])) UGraph: v1 at [1 1] component=0 v2 at [2 2] component=1 v3 at [3 3] component=2
- Seealso:
- Graph.build_kdtree()[source]
Build a spatial index of the vertices, used by
closest()andnear()- Raises:
ValueError – the metric is not one of the built-in
"L1","L2"or"SE2", or"SE2"and the coordinates are not 3 elements, or no vertex has a coordinateImportError – scipy is not installed
Finding the nearest vertex to a point is a pass over all the vertices, which is the cost of a query on a large graph that is queried often. A k-d tree answers in a time that grows more slowly, at the cost of building it once.
closest()andnear()use the index if it has been built, and fall back to the pass over the vertices if it has not or if the graph has changed.The index is for the built-in metrics
"L1","L2"and"SE2"(with any weight, seeSE2Metric), and results are the same as without it, except that when two vertices are exactly the same distance the one returned may differ.Warning
The index is dropped when a vertex is added or removed or when
vertex.coordis assigned (including+=), andclosest()is then correct but slow, until it is built again. It cannot see a coordinate that is changed in place,vertex.coord[0] = x. Do not change vertices in place after building, or build again.The index is not copied or pickled with the graph.
Note
This needs scipy. pgraph does not list it as a dependency and imports it only when this method is called, but spatialmath-python, which pgraph does depend on, installs it.
- Seealso:
- Return type:
- Graph.subgraph(vertices)[source]
Subgraph induced by a set of vertices
- Parameters:
vertices (iterable of Vertex subclass or str, or ndarray(n) of bool) – vertices of this graph, or their names, or a boolean mask with one element per vertex
- Raises:
ValueError – a vertex is not in this graph, or a mask is not the length of the number of vertices
KeyError – a name is not that of a vertex in this graph
TypeError – an item is neither a vertex nor a string
- Returns:
a new graph of the same kind
- Return type:
The new graph has the given vertices and every edge of this graph that has both its end vertices among them. An edge that has one end outside is removed, so the edges that cross the border of a region are dropped. Parallel edges and loops are kept.
Like
copy()the new graph is independent of this one: its vertices and edges are new objects that keep their class, name, coordinate, cost and any attributes that were added, and the graph keeps its metric, heuristic and vertex and edge classes. They are in the order of this graph, howeververticeswas ordered. Names are kept, so the vertexg[name]has its counterparth[name].>>> from pgraph import UGraph >>> g = UGraph() >>> v = [g.add_vertex(coord=[i, 0], name=f"v{i}") for i in range(4)] >>> for i in range(3): ... g.add_edge(v[i], v[i + 1]) Edge{[v0] -- [v1], cost=1} Edge{[v1] -- [v2], cost=1} Edge{[v2] -- [v3], cost=1} >>> h = g.subgraph([v[1], v[2]]) >>> print(h) UGraph: 2 vertices, 1 edge, 1 component >>> h.edges() [Edge{[v1] -- [v2], cost=1}]
It is a single pass over the graph. The vertices might be a component, the vertices that satisfy a test, or those in a region of an embedded graph from
within().A boolean mask, such as the result of a comparison on
coordinates(), selects the vertices by their position in the graph, theTrueelements being kept:h = g.subgraph(g.coordinates()[0] > 0) # the vertices with x > 0
The mask must be of boolean type (a list of
boolalso works) and of lengthg.n. A vector of 0 and 1 of integer type is not accepted, since it could as well be a list of indices.- Seealso:
- Graph.validate(embedded=False)[source]
Check that the graph’s records agree with each other
- Parameters:
embedded (
bool) – also report a vertex that has no coordinate, defaults to False- Returns:
a description of each inconsistency found, empty if the graph is consistent
- Return type:
A graph keeps the same facts in several places: its set of edges, each vertex’s list of outgoing edges (and, for a directed graph, of incoming ones), each edge’s two vertices, each vertex’s pointer back to its graph, and the table of vertex names. The library keeps them in step, so a graph built and edited through the public methods is always valid. This is a check for tests, for debugging, and for code that reaches into the internals.
It does not change the graph, and takes time proportional to the number of vertices plus edges. It reports every inconsistency, not only the first.
The checks are:
the vertex list holds only vertices, once each, and the edge set holds only edges
every edge in the graph has two vertices, both in this graph, and is an instance of
edge_classevery vertex points back at this graph, is an instance of
vertex_class, is in the name table under its own name, and has a coordinate of the required length ifdimwas givenevery coordinate is a finite one-dimensional numeric vector, and if
dimwas not given they all have the same lengthwith
embedded=True, every vertex has a coordinate. A graph with no coordinates is valid, but one that is half embedded rarely isevery entry of the name table is a vertex of this graph and is filed under its own name
each edge is in the right vertex lists, the right number of times: both ends for an undirected edge (twice at one vertex for a self-loop), the source’s outgoing and the target’s incoming list for a directed edge
no vertex list holds an edge that is not in the graph
>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(name='v1') >>> v2 = g.add_vertex(name='v2') >>> g.add_edge(v1, v2) Edge{[v1] -- [v2], cost=None} >>> print(g.validate()) []
- Seealso:
- property Vertex.name: str | None
Name of the vertex
- Returns:
the name
- Return type:
str or None
The name is the vertex’s key in its graph:
g[name]andname in glook it up. A vertex that is not in a graph can be given any name, or none. For one that is in a graph:assigning a new name updates the graph’s name table, so the vertex is then found under the new name and not the old
a name already used by another vertex in the graph raises
ValueErrorand changes nothingthe name cannot be cleared
Changed in version 1.0: Renaming a vertex in a graph used to leave the graph’s name table stale, so lookups by the new name failed.
- Seealso:
- property Vertex.label: int | None
Number of the graph component that contains the vertex
- Returns:
component number, counted from 0, or None if the vertex is not in a graph
- Return type:
int or None
Assigned by the graph, which finds its components when this is read after the graph has changed, as it does for
Graph.nc,Graph.component()andGraph.samecomponent(). The numbering is by the first vertex of each component, in the order the vertices were added. It is read-only.Changed in version 1.0: Read-only. It was a plain attribute, but it is computed from the graph’s structure and meaningless to assign. Reading it finds the components; in 0.6.5 it was
Noneuntil something else had.- Seealso:
- property Vertex.x: float
The x-coordinate of an embedded vertex
- Raises:
ValueError – the vertex has no coordinate
- Returns:
The x-coordinate
- Return type:
- property Vertex.y: float
The y-coordinate of an embedded vertex
- Raises:
ValueError – the vertex has no coordinate
- Returns:
The y-coordinate
- Return type:
- property Vertex.z: float
The z-coordinate of an embedded vertex
- Raises:
ValueError – the vertex has no coordinate
- Returns:
The z-coordinate
- Return type:
- Vertex.adjacent()[source]
Vertices adjacent to this vertex
- Returns:
the vertices at the far end of this vertex’s edges
- Return type:
list of Vertex subclass
v.adjacent()is a list of the vertices that this vertex is joined to by an edge. “Adjacent” is the graph-theory term, and it is the relation thatGraph.adjacency()tabulates: rowiof the adjacency matrix is non-zero exactly at the columns of the vertices returned byg[i].adjacent().Note
For a directed graph these are the vertices at the head of the edges leaving this vertex, the successors.
There is one entry per edge, so a vertex joined by
kparallel edges appearsktimes. In an undirected graph a self-loop lists the vertex itself twice, once for each end.neighboursandneighborsare aliases, the same method under the British and American spellings, and are not deprecated.
>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[1,1], name='v2') >>> v3 = g.add_vertex(coord=[2,2], name='v3') >>> g.add_edge(v1, v2) Edge{[v1] -- [v2], cost=1.414} >>> g.add_edge(v1, v3) Edge{[v1] -- [v3], cost=2.828} >>> print(v1.adjacent()) [Vertex[v2, coord=(1, 1)], Vertex[v3, coord=(2, 2)]]
- Vertex.isadjacent(vertex)[source]
Test if a vertex is adjacent to this vertex
- Parameters:
vertex (Vertex subclass) – vertex reference
- Returns:
true if
vertexis adjacent- Return type:
For a directed graph this is true only if there is an edge from
selftovertex.isneighbourandisneighborare aliases.>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[1,1], name='v2') >>> v3 = g.add_vertex(coord=[2,2], name='v3') >>> g.add_edge(v1, v2) Edge{[v1] -- [v2], cost=1.414} >>> print(v1.isadjacent(v2)) True >>> print(v1.isadjacent(v3)) False
- Seealso:
- Vertex.successors()[source]
Vertices that this vertex has an edge to
- Returns:
the vertices at the head of the outgoing edges
- Return type:
list of Vertex subclass
This is
adjacent(), under its directed-graph name.- Seealso:
- Vertex.predecessors()[source]
Vertices that have an edge to this vertex
- Returns:
the vertices at the tail of the incoming edges
- Return type:
list of Vertex subclass
There is one entry per edge, so a vertex joined by
kparallel edges appearsktimes. For an undirected graph this isadjacent().>>> from pgraph import DGraph >>> g = DGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[1,1], name='v2') >>> v3 = g.add_vertex(coord=[2,2], name='v3') >>> g.add_edge(v1, v3) Edge{[v1] -> [v3], cost=2.828} >>> g.add_edge(v2, v3) Edge{[v2] -> [v3], cost=1.414} >>> print(v3.predecessors()) [Vertex[v1, coord=(0, 0)], Vertex[v2, coord=(1, 1)]]
- Seealso:
- Vertex.edges()[source]
The edges at the vertex, in the order they were connected
- Returns:
the edges at this vertex
- Return type:
list of
Edge
For a directed graph these are the edges leaving this vertex. For an undirected graph they are the edges incident on it, whichever end it is. A self-loop in an undirected graph is listed twice, once for each end.
The list is a new one on every call, in the order the edges were connected, so changing it does not change the graph, and edges can be removed from the graph while iterating over it. Use
Graph.remove_edge()to remove one.>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[1,1], name='v2') >>> g.add_edge(v1, v2) Edge{[v1] -- [v2], cost=1.414} >>> print(v1.edges()) [Edge{[v1] -- [v2], cost=1.414}]
- Vertex.out_edges()[source]
The edges leaving the vertex
- Returns:
the outgoing edges, in the order they were connected
- Return type:
list of
Edge
This is a new list, and is the same as
edges(): for an undirected graph every incident edge is outgoing.
- Vertex.in_edges()[source]
The edges entering the vertex
- Returns:
the incoming edges, in the order they were connected
- Return type:
list of
Edge
This is a new list. For an undirected graph every incident edge is incoming, so it equals
edges().- Seealso:
- Vertex.incidences()[source]
Adjacent vertices paired with the edges that lead to them
- Returns:
a
(vertex, edge)pair for each edge leaving this vertex, in the order the edges were connected- Return type:
v.incidences()isadjacent()andout_edges()taken together: the first element of each pair is the vertex at the far end of the edge, the second is the edge itself, so itsEdge.costandEdge.dataare at hand. It is equivalent to:but is built in a single pass, which is what the path-planning methods want when they expand a vertex.
Note
In graph-theory terms
adjacent()is a vertex’s adjacency list,out_edges()its incidence list, and this is the two joined.For a directed graph the pairs are for the edges leaving this vertex. The ones entering it are
in_edges(), and the vertices at their tails arepredecessors().There is one pair per edge, so parallel edges to the same vertex give repeated vertices with different edges, and an undirected self-loop gives the pair twice, once for each end.
>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[1,1], name='v2') >>> v3 = g.add_vertex(coord=[2,2], name='v3') >>> g.add_edge(v1, v2, cost=1.5) Edge{[v1] -- [v2], cost=1.5} >>> g.add_edge(v1, v3, cost=2.5) Edge{[v1] -- [v3], cost=2.5} >>> for vertex, edge in v1.incidences(): ... print(vertex.name, edge.cost) v2 1.5 v3 2.5
- Vertex.edgeto(dest)[source]
The edge from this vertex to an adjacent vertex
- Parameters:
dest (
Vertexsubclass) – an adjacent vertex- Raises:
ValueError –
destis not adjacent to this vertex- Returns:
the edge from this vertex to
dest- Return type:
Note
For a directed graph
destmust be at the arrow end of the edgeIf there are parallel edges to
destthe cheapest is returned (the first, if none of them has a cost)
>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[1,1], name='v2') >>> g.add_edge(v1, v2, cost=1.414) Edge{[v1] -- [v2], cost=1.414} >>> print(v1.edgeto(v2)) Edge{[v1] -- [v2], cost=1.414}
- Seealso:
- Vertex.edges_to(dest)[source]
All the edges from this vertex to another vertex
- Parameters:
dest (
Vertexsubclass) – the vertex at the far end- Returns:
every edge from this vertex to
dest, in the order they were connected, which is empty if there are none- Return type:
This is how to see parallel edges.
edgeto()returns the one edge that a path would use, the cheapest, whereas this returns them all.Note
For a directed graph only the edges leaving this vertex for
destare included, soa.edges_to(b)andb.edges_to(a)are different listsFor an undirected graph the edges are found from either end, whichever vertex they were connected from
A self-loop is listed once, although its vertex holds it twice (see
edges())Unlike
edgeto(), there being no edge is not an error, the list is emptyThe list is a new one, so changing it does not change the graph
>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[1,1], name='v2') >>> g.add_edge(v1, v2, cost=3) Edge{[v1] -- [v2], cost=3} >>> g.add_edge(v1, v2, cost=1) Edge{[v1] -- [v2], cost=1} >>> print(v1.edges_to(v2)) [Edge{[v1] -- [v2], cost=3}, Edge{[v1] -- [v2], cost=1}] >>> print(v1.edgeto(v2)) Edge{[v1] -- [v2], cost=1}
- Seealso:
- property Vertex.degree: int
Degree of vertex
- Returns:
degree of the vertex
- Return type:
The number of edge ends at this vertex: the edges leaving it plus the edges entering it, so a self-loop counts twice. The degrees of all the vertices of a graph sum to twice its number of edges.
For an undirected graph this is the number of incident edges. For a directed graph it is
in_degree+out_degree.Changed in version 1.0: For a
DGraphthis used to count only the edges leaving the vertex. It is now the graph-theory degree, in plus out. Useout_degreefor the previous value.>>> from pgraph import DGraph >>> g = DGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[1,1], name='v2') >>> v3 = g.add_vertex(coord=[2,2], name='v3') >>> g.add_edge(v1, v2) Edge{[v1] -> [v2], cost=1.414} >>> g.add_edge(v1, v3) Edge{[v1] -> [v3], cost=2.828} >>> g.add_edge(v3, v1) Edge{[v3] -> [v1], cost=2.828} >>> print(v1.degree, v1.in_degree, v1.out_degree) 3 1 2
For graphs with parallel edges or loops
Every edge is counted, so
kparallel edges to the same vertex addk. A self-loop adds 2, for both kinds of graph: it has two ends.
- property Vertex.in_degree: int
Number of edges entering the vertex
- Returns:
in-degree of the vertex
- Return type:
For an undirected graph every incident edge both leaves and enters, so this equals
degree.- Seealso:
- property Vertex.out_degree: int
Number of edges leaving the vertex
- Returns:
out-degree of the vertex
- Return type:
For an undirected graph every incident edge both leaves and enters, so this equals
degree.- Seealso:
- Vertex.distance(coord)[source]
Distance from vertex to point
- Parameters:
- Returns:
distance
- Return type:
Distance is computed according to the graph’s metric. The vertex must belong to a graph, since the metric is a property of the graph, not the vertex.
>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[3,4], name='v2') >>> print(v1.distance(v2)) 5.0
- Seealso:
- Vertex.heuristic_distance(v2)[source]
Heuristic distance to another vertex
- Parameters:
v2 (Vertex subclass) – the other vertex
- Returns:
heuristic distance between this vertex and
v2- Return type:
Distance is computed according to the graph’s heuristic, see
Graph.heuristic(). The vertex must belong to a graph, since the heuristic is a property of the graph, not the vertex.>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[1, 2], name='v1') >>> v2 = g.add_vertex(coord=[4, 3], name='v2') >>> print(v1.heuristic_distance(v2)) 3.1622776601683795
- Seealso:
- Vertex.closest()[source]
Return the closest vertex to this vertex
- Returns:
closest vertex and its distance
- Return type:
Vertex subclass or None, float
Equivalent to
self._graph.closest(self.coord). The vertex must belong to a graph, sinceclosest()searches that graph’s other vertices.>>> from pgraph import UGraph >>> g = UGraph() >>> v1 = g.add_vertex(coord=[0,0], name='v1') >>> v2 = g.add_vertex(coord=[1,1], name='v2') >>> v3 = g.add_vertex(coord=[2,3], name='v3') >>> v4 = g.add_vertex(coord=[4,3], name='v4') >>> print(v1.closest()) (Vertex[v1, coord=(0, 0)], 0.0)
- Seealso: