pgraph-python 0.6.2__py3-none-any.whl → 0.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pgraph/PGraph.py +180 -148
- {pgraph_python-0.6.2.dist-info → pgraph_python-0.6.4.dist-info}/METADATA +43 -34
- pgraph_python-0.6.4.dist-info/RECORD +6 -0
- {pgraph_python-0.6.2.dist-info → pgraph_python-0.6.4.dist-info}/WHEEL +1 -2
- pgraph_python-0.6.2.dist-info/RECORD +0 -9
- pgraph_python-0.6.2.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- tests/test_graph.py +0 -737
- {pgraph_python-0.6.2.dist-info → pgraph_python-0.6.4.dist-info/licenses}/LICENSE +0 -0
pgraph/PGraph.py
CHANGED
|
@@ -11,7 +11,6 @@ import webbrowser
|
|
|
11
11
|
from spatialmath.base.graphics import axes_logic
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
class PGraph(ABC):
|
|
16
15
|
|
|
17
16
|
def __init__(self, arg=None, metric=None, heuristic=None, verbose=False):
|
|
@@ -20,8 +19,10 @@ class PGraph(ABC):
|
|
|
20
19
|
self._vertexdict = {}
|
|
21
20
|
self._edgelist = set()
|
|
22
21
|
self._verbose = verbose
|
|
22
|
+
self._ncomponents = 0
|
|
23
|
+
self._connectivitychange = False
|
|
23
24
|
if metric is None:
|
|
24
|
-
self.metric =
|
|
25
|
+
self.metric = "L2"
|
|
25
26
|
else:
|
|
26
27
|
self.metric = metric
|
|
27
28
|
if heuristic is None:
|
|
@@ -108,11 +109,11 @@ class PGraph(ABC):
|
|
|
108
109
|
"""
|
|
109
110
|
|
|
110
111
|
if A.shape[0] != A.shape[1]:
|
|
111
|
-
raise ValueError(
|
|
112
|
+
raise ValueError("Adjacency matrix must be square")
|
|
112
113
|
if names is not None and len(names) != A.shape[0]:
|
|
113
|
-
raise ValueError(
|
|
114
|
+
raise ValueError("length of names must match dimension of adjacency matrix")
|
|
114
115
|
if coords is not None and coords.shape[0] != A.shape[0]:
|
|
115
|
-
|
|
116
|
+
raise ValueError("coords must have same number of rows as adjacency matrix")
|
|
116
117
|
|
|
117
118
|
g = cls()
|
|
118
119
|
|
|
@@ -128,21 +129,20 @@ class PGraph(ABC):
|
|
|
128
129
|
if isinstance(g, UGraph):
|
|
129
130
|
# undirected graph
|
|
130
131
|
for i in range(A.shape[0]):
|
|
131
|
-
for j in range(i+1, A.shape[1]):
|
|
132
|
+
for j in range(i + 1, A.shape[1]):
|
|
132
133
|
if A[i, j] > 0:
|
|
133
|
-
g[i].connect(g[j], cost=A[i,j])
|
|
134
|
+
g[i].connect(g[j], cost=A[i, j])
|
|
134
135
|
else:
|
|
135
136
|
# directed graph
|
|
136
137
|
for i in range(A.shape[0]):
|
|
137
138
|
for j in range(A.shape[1]):
|
|
138
139
|
if A[i, j] > 0:
|
|
139
140
|
if i == j:
|
|
140
|
-
raise ValueError(
|
|
141
|
-
g[i].connect(g[j], cost=A[i,j])
|
|
141
|
+
raise ValueError("loops in graph not supported")
|
|
142
|
+
g[i].connect(g[j], cost=A[i, j])
|
|
142
143
|
|
|
143
144
|
return g
|
|
144
145
|
|
|
145
|
-
|
|
146
146
|
def copy(self):
|
|
147
147
|
"""
|
|
148
148
|
Deepcopy of graph
|
|
@@ -197,7 +197,7 @@ class PGraph(ABC):
|
|
|
197
197
|
|
|
198
198
|
Create an edge between a vertex pair and adds it to the graph.
|
|
199
199
|
|
|
200
|
-
This is a graph centric way of creating an edge. The
|
|
200
|
+
This is a graph centric way of creating an edge. The
|
|
201
201
|
alternative is the ``connect`` method of a vertex.
|
|
202
202
|
|
|
203
203
|
:seealso: :meth:`Edge.connect` :meth:`Vertex.connect`
|
|
@@ -205,12 +205,12 @@ class PGraph(ABC):
|
|
|
205
205
|
if isinstance(v1, str):
|
|
206
206
|
v1 = self[v1]
|
|
207
207
|
elif not isinstance(v1, Vertex):
|
|
208
|
-
raise TypeError(
|
|
208
|
+
raise TypeError("v1 must be Vertex subclass or string name")
|
|
209
209
|
if isinstance(v2, str):
|
|
210
210
|
v2 = self[v2]
|
|
211
211
|
elif not isinstance(v2, Vertex):
|
|
212
|
-
raise TypeError(
|
|
213
|
-
|
|
212
|
+
raise TypeError("v2 must be Vertex subclass or string name")
|
|
213
|
+
|
|
214
214
|
if self._verbose:
|
|
215
215
|
print(f"New edge from {v1.name} to {v2.name}")
|
|
216
216
|
return v1.connect(v2, **kwargs)
|
|
@@ -258,15 +258,15 @@ class PGraph(ABC):
|
|
|
258
258
|
self._vertexlist.remove(x)
|
|
259
259
|
del self._vertexdict[x.name]
|
|
260
260
|
else:
|
|
261
|
-
raise TypeError(
|
|
261
|
+
raise TypeError("expecting Edge or Vertex")
|
|
262
262
|
|
|
263
263
|
def show(self):
|
|
264
|
-
print(
|
|
264
|
+
print("vertices:")
|
|
265
265
|
for v in self._vertexlist:
|
|
266
|
-
print(
|
|
267
|
-
print(
|
|
266
|
+
print(" " + str(v))
|
|
267
|
+
print("edges:")
|
|
268
268
|
for e in self._edgelist:
|
|
269
|
-
print(
|
|
269
|
+
print(" " + str(e))
|
|
270
270
|
|
|
271
271
|
@property
|
|
272
272
|
def n(self):
|
|
@@ -302,12 +302,15 @@ class PGraph(ABC):
|
|
|
302
302
|
- A graph coloring algorithm is run if the graph connectivity
|
|
303
303
|
has changed.
|
|
304
304
|
|
|
305
|
-
.. note:: A lazy approach is used, and if a connectivity changing
|
|
305
|
+
.. note:: A lazy approach is used, and if a connectivity changing
|
|
306
306
|
operation has been performed since the last call, the graph
|
|
307
|
-
coloring algorithm is run which is potentially expensive for
|
|
307
|
+
coloring algorithm is run which is potentially expensive for
|
|
308
308
|
a large graph.
|
|
309
309
|
"""
|
|
310
|
-
self._graphcolor()
|
|
310
|
+
n = self._graphcolor()
|
|
311
|
+
if n is not None:
|
|
312
|
+
self._ncomponents = n
|
|
313
|
+
|
|
311
314
|
return self._ncomponents
|
|
312
315
|
|
|
313
316
|
def _metricfunc(self, metric):
|
|
@@ -317,7 +320,7 @@ class PGraph(ABC):
|
|
|
317
320
|
|
|
318
321
|
def L2(v):
|
|
319
322
|
return np.linalg.norm(v)
|
|
320
|
-
|
|
323
|
+
|
|
321
324
|
def SE2(v):
|
|
322
325
|
# wrap angle to range [-pi, pi)
|
|
323
326
|
v[2] = (v[2] + np.pi) % (2 * np.pi) - np.pi
|
|
@@ -326,14 +329,14 @@ class PGraph(ABC):
|
|
|
326
329
|
if callable(metric):
|
|
327
330
|
return metric
|
|
328
331
|
elif isinstance(metric, str):
|
|
329
|
-
if metric ==
|
|
332
|
+
if metric == "L1":
|
|
330
333
|
return L1
|
|
331
|
-
elif metric ==
|
|
334
|
+
elif metric == "L2":
|
|
332
335
|
return L2
|
|
333
|
-
elif metric ==
|
|
336
|
+
elif metric == "SE2":
|
|
334
337
|
return SE2
|
|
335
338
|
else:
|
|
336
|
-
raise ValueError(
|
|
339
|
+
raise ValueError("unknown metric")
|
|
337
340
|
|
|
338
341
|
@property
|
|
339
342
|
def metric(self):
|
|
@@ -355,7 +358,7 @@ class PGraph(ABC):
|
|
|
355
358
|
:param metric: distance metric
|
|
356
359
|
:type metric: callable or str
|
|
357
360
|
|
|
358
|
-
This is a function of a vector and returns a scalar. It can be
|
|
361
|
+
This is a function of a vector and returns a scalar. It can be
|
|
359
362
|
user defined function or a string:
|
|
360
363
|
|
|
361
364
|
- 'L1' is the norm :math:`L_1 = \Sigma_i | v_i |`
|
|
@@ -363,7 +366,7 @@ class PGraph(ABC):
|
|
|
363
366
|
- 'SE2' is a mixed norm for vectors :math:`(x, y, \theta)` and
|
|
364
367
|
is :math:`\sqrt{x^2 + y^2 + \bar{\theta}^2}` where :math:`\bar{\theta}`
|
|
365
368
|
is :math:`\theta` wrapped to the interval :math:`[-\pi, \pi)`
|
|
366
|
-
|
|
369
|
+
|
|
367
370
|
The metric is used by :meth:`closest` and :meth:`distance`
|
|
368
371
|
"""
|
|
369
372
|
self._metric = self._metricfunc(metric)
|
|
@@ -388,7 +391,7 @@ class PGraph(ABC):
|
|
|
388
391
|
:param metric: heuristic distance metric
|
|
389
392
|
:type metric: callable or str
|
|
390
393
|
|
|
391
|
-
This is a function of a vector and returns a scalar. It can be
|
|
394
|
+
This is a function of a vector and returns a scalar. It can be
|
|
392
395
|
user defined function or a string:
|
|
393
396
|
|
|
394
397
|
- 'L1' is the norm :math:`L_1 = \Sigma_i | v_i |`
|
|
@@ -408,7 +411,7 @@ class PGraph(ABC):
|
|
|
408
411
|
if vertex.label is not None:
|
|
409
412
|
ss += " component={vertex.label}"
|
|
410
413
|
s.append(ss)
|
|
411
|
-
return
|
|
414
|
+
return "\n".join(s)
|
|
412
415
|
|
|
413
416
|
def __getitem__(self, i):
|
|
414
417
|
"""
|
|
@@ -421,7 +424,7 @@ class PGraph(ABC):
|
|
|
421
424
|
|
|
422
425
|
Retrieve a vertex by index or name:
|
|
423
426
|
|
|
424
|
-
-``g[i]`` is the i'th vertex in the graph. This reflects the order of
|
|
427
|
+
-``g[i]`` is the i'th vertex in the graph. This reflects the order of
|
|
425
428
|
addition to the graph.
|
|
426
429
|
-``g[s]`` is vertex named ``s``
|
|
427
430
|
-``g[v]`` is ``v`` where ``v`` is a ``Vertex`` subclass
|
|
@@ -474,7 +477,7 @@ class PGraph(ABC):
|
|
|
474
477
|
|
|
475
478
|
:seealso: :meth:`metric`
|
|
476
479
|
"""
|
|
477
|
-
min_dist = np.
|
|
480
|
+
min_dist = np.inf
|
|
478
481
|
min_which = None
|
|
479
482
|
|
|
480
483
|
for vertex in self:
|
|
@@ -482,7 +485,7 @@ class PGraph(ABC):
|
|
|
482
485
|
if d < min_dist:
|
|
483
486
|
min_dist = d
|
|
484
487
|
min_which = vertex
|
|
485
|
-
|
|
488
|
+
|
|
486
489
|
return min_which, min_dist
|
|
487
490
|
|
|
488
491
|
def edges(self):
|
|
@@ -504,7 +507,17 @@ class PGraph(ABC):
|
|
|
504
507
|
"""
|
|
505
508
|
return self._edgelist
|
|
506
509
|
|
|
507
|
-
def plot(
|
|
510
|
+
def plot(
|
|
511
|
+
self,
|
|
512
|
+
colorcomponents=True,
|
|
513
|
+
force2d=False,
|
|
514
|
+
vopt={},
|
|
515
|
+
eopt={},
|
|
516
|
+
text={},
|
|
517
|
+
block=False,
|
|
518
|
+
grid=True,
|
|
519
|
+
ax=None,
|
|
520
|
+
):
|
|
508
521
|
"""
|
|
509
522
|
Plot the graph
|
|
510
523
|
|
|
@@ -528,10 +541,9 @@ class PGraph(ABC):
|
|
|
528
541
|
which are the vertex names. If ``text`` is None default formatting is
|
|
529
542
|
used. If ``text`` is False no labels are added.
|
|
530
543
|
"""
|
|
531
|
-
vopt = {**dict(marker=
|
|
544
|
+
vopt = {**dict(marker="o", markersize=12), **vopt}
|
|
532
545
|
eopt = {**dict(linewidth=3), **eopt}
|
|
533
546
|
|
|
534
|
-
|
|
535
547
|
if colorcomponents:
|
|
536
548
|
color = plt.cm.coolwarm(np.linspace(0, 1, self.nc))
|
|
537
549
|
|
|
@@ -547,8 +559,12 @@ class PGraph(ABC):
|
|
|
547
559
|
if colorcomponents:
|
|
548
560
|
ax.plot(vertex.x, vertex.y, color=color[c, :], **vopt)
|
|
549
561
|
for v in vertex.neighbours():
|
|
550
|
-
ax.plot(
|
|
551
|
-
|
|
562
|
+
ax.plot(
|
|
563
|
+
[vertex.x, v.x],
|
|
564
|
+
[vertex.y, v.y],
|
|
565
|
+
color=color[c, :],
|
|
566
|
+
**eopt,
|
|
567
|
+
)
|
|
552
568
|
else:
|
|
553
569
|
ax.plot(vertex.x, vertex.y, **vopt)
|
|
554
570
|
for v in vertex.neighbours():
|
|
@@ -561,16 +577,32 @@ class PGraph(ABC):
|
|
|
561
577
|
# for each component
|
|
562
578
|
for vertex in self.component(c):
|
|
563
579
|
if text is not False:
|
|
564
|
-
ax.text(
|
|
580
|
+
ax.text(
|
|
581
|
+
vertex.x, vertex.y, vertex.z, " " + vertex.name, **text
|
|
582
|
+
)
|
|
565
583
|
if colorcomponents:
|
|
566
|
-
ax.plot(
|
|
584
|
+
ax.plot(
|
|
585
|
+
vertex.x,
|
|
586
|
+
vertex.y,
|
|
587
|
+
vertex.z,
|
|
588
|
+
**{**dict(color=color[c, :]), **vopt},
|
|
589
|
+
)
|
|
567
590
|
for v in vertex.neighbours():
|
|
568
|
-
ax.plot(
|
|
569
|
-
|
|
591
|
+
ax.plot(
|
|
592
|
+
[vertex.x, v.x],
|
|
593
|
+
[vertex.y, v.y],
|
|
594
|
+
[vertex.z, v.z],
|
|
595
|
+
**{**dict(color=color[c, :]), **eopt},
|
|
596
|
+
)
|
|
570
597
|
else:
|
|
571
598
|
ax.plot(vertex.x, vertex.y, **vopt)
|
|
572
599
|
for v in vertex.neighbours():
|
|
573
|
-
ax.plot(
|
|
600
|
+
ax.plot(
|
|
601
|
+
[vertex.x, v.x],
|
|
602
|
+
[vertex.y, v.y],
|
|
603
|
+
[vertex.z, v.z],
|
|
604
|
+
**eopt,
|
|
605
|
+
)
|
|
574
606
|
# if nc > 1:
|
|
575
607
|
# # add a colorbar
|
|
576
608
|
# plt.colorbar()
|
|
@@ -594,12 +626,13 @@ class PGraph(ABC):
|
|
|
594
626
|
"""
|
|
595
627
|
for i in range(len(path)):
|
|
596
628
|
if i < len(path) - 1:
|
|
597
|
-
e = path[i].edgeto(path[i+1])
|
|
629
|
+
e = path[i].edgeto(path[i + 1])
|
|
598
630
|
self.highlight_edge(e, **kwargs)
|
|
599
631
|
self.highlight_vertex(path[i], **kwargs)
|
|
600
|
-
|
|
632
|
+
if block is not None:
|
|
633
|
+
plt.show(block=block)
|
|
601
634
|
|
|
602
|
-
def highlight_edge(self, edge, scale=2, color=
|
|
635
|
+
def highlight_edge(self, edge, scale=2, color="r", alpha=0.5):
|
|
603
636
|
"""
|
|
604
637
|
Highlight an edge in the graph
|
|
605
638
|
|
|
@@ -613,10 +646,11 @@ class PGraph(ABC):
|
|
|
613
646
|
"""
|
|
614
647
|
p1 = edge.v1
|
|
615
648
|
p2 = edge.v2
|
|
616
|
-
plt.plot(
|
|
617
|
-
|
|
649
|
+
plt.plot(
|
|
650
|
+
[p1.x, p2.x], [p1.y, p2.y], color=color, linewidth=3 * scale, alpha=alpha
|
|
651
|
+
)
|
|
618
652
|
|
|
619
|
-
def highlight_vertex(self, vertex, scale=2, color=
|
|
653
|
+
def highlight_vertex(self, vertex, scale=2, color="r", alpha=0.5):
|
|
620
654
|
"""
|
|
621
655
|
Highlight a vertex in the graph
|
|
622
656
|
|
|
@@ -632,11 +666,11 @@ class PGraph(ABC):
|
|
|
632
666
|
for n in vertex:
|
|
633
667
|
if isinstance(n, str):
|
|
634
668
|
n = self[n]
|
|
635
|
-
plt.plot(n.x, n.y,
|
|
636
|
-
markersize=12 * scale, alpha=alpha)
|
|
669
|
+
plt.plot(n.x, n.y, "o", color=color, markersize=12 * scale, alpha=alpha)
|
|
637
670
|
else:
|
|
638
|
-
plt.plot(
|
|
639
|
-
|
|
671
|
+
plt.plot(
|
|
672
|
+
vertex.x, vertex.y, "o", color=color, markersize=12 * scale, alpha=alpha
|
|
673
|
+
)
|
|
640
674
|
|
|
641
675
|
def dotfile(self, filename=None, direction=None):
|
|
642
676
|
"""
|
|
@@ -645,15 +679,19 @@ class PGraph(ABC):
|
|
|
645
679
|
:param filename: filename to save graph to, defaults to None
|
|
646
680
|
:type filename: str, optional
|
|
647
681
|
|
|
648
|
-
``g.dotfile()`` creates the specified file which contains the
|
|
649
|
-
|
|
650
|
-
is to the console.
|
|
682
|
+
``g.dotfile()`` creates the specified file which contains the `GraphViz
|
|
683
|
+
<https://graphviz.org>`_ code to represent the embedded graph. By default
|
|
684
|
+
output is to the console.
|
|
651
685
|
|
|
652
686
|
.. note::
|
|
653
687
|
|
|
654
688
|
- The graph is undirected if it is a subclass of ``UGraph``
|
|
655
689
|
- The graph is directed if it is a subclass of ``DGraph``
|
|
656
|
-
|
|
690
|
+
|
|
691
|
+
The ``graphviz`` formatters ``dot`` and ``neato`` can be used to render the
|
|
692
|
+
graph in various formats including PDF, PNG, JPG, GIF, SVG, etc. The formmatters are
|
|
693
|
+
similar, but ``dot`` is better suited for directed graphs while ``neato`` is better
|
|
694
|
+
suited for undirected graphs.
|
|
657
695
|
|
|
658
696
|
.. note:: If ``filename`` is a file object then the file will *not*
|
|
659
697
|
be closed after the GraphViz model is written.
|
|
@@ -681,8 +719,12 @@ class PGraph(ABC):
|
|
|
681
719
|
if vertex.coord is None:
|
|
682
720
|
print(' "{:s}"'.format(vertex.name), file=f)
|
|
683
721
|
else:
|
|
684
|
-
print(
|
|
685
|
-
|
|
722
|
+
print(
|
|
723
|
+
' "{:s}" [pos="{:.5g},{:.5g}"]'.format(
|
|
724
|
+
vertex.name, vertex.coord[0], vertex.coord[1]
|
|
725
|
+
),
|
|
726
|
+
file=f,
|
|
727
|
+
)
|
|
686
728
|
print(file=f)
|
|
687
729
|
# add the edges
|
|
688
730
|
for e in self.edges():
|
|
@@ -691,7 +733,7 @@ class PGraph(ABC):
|
|
|
691
733
|
else:
|
|
692
734
|
print(' "{:s}" -- "{:s}"'.format(e.v1.name, e.v2.name), file=f)
|
|
693
735
|
|
|
694
|
-
print(
|
|
736
|
+
print("}", file=f)
|
|
695
737
|
|
|
696
738
|
if filename is None or isinstance(filename, str):
|
|
697
739
|
f.close() # noqa
|
|
@@ -734,7 +776,7 @@ class PGraph(ABC):
|
|
|
734
776
|
:return: average degree
|
|
735
777
|
:rtype: float
|
|
736
778
|
|
|
737
|
-
Average degree is :math:`2 E / N` for an undirected graph and
|
|
779
|
+
Average degree is :math:`2 E / N` for an undirected graph and
|
|
738
780
|
:math:`E / N` for a directed graph where :math:`E` is the total number of
|
|
739
781
|
edges and :math:`N` is the number of vertices.
|
|
740
782
|
|
|
@@ -744,7 +786,7 @@ class PGraph(ABC):
|
|
|
744
786
|
elif isinstance(self, UGraph):
|
|
745
787
|
return 2 * len(self.edges()) / self.n
|
|
746
788
|
|
|
747
|
-
# --------------------------------------------------------------------------- #
|
|
789
|
+
# --------------------------------------------------------------------------- #
|
|
748
790
|
|
|
749
791
|
# MATRIX REPRESENTATIONS
|
|
750
792
|
|
|
@@ -762,7 +804,7 @@ class PGraph(ABC):
|
|
|
762
804
|
|
|
763
805
|
- Laplacian is always positive-semidefinite.
|
|
764
806
|
- Laplacian has at least one zero eigenvalue.
|
|
765
|
-
- The number of zero-valued eigenvalues is the number of connected
|
|
807
|
+
- The number of zero-valued eigenvalues is the number of connected
|
|
766
808
|
components in the graph.
|
|
767
809
|
|
|
768
810
|
:seealso: :meth:`adjacency` :meth:`incidence` :meth:`degree`
|
|
@@ -779,7 +821,7 @@ class PGraph(ABC):
|
|
|
779
821
|
The average vertex connectivity is::
|
|
780
822
|
|
|
781
823
|
mean(g.connectivity())
|
|
782
|
-
|
|
824
|
+
|
|
783
825
|
and the minimum vertex connectivity is::
|
|
784
826
|
|
|
785
827
|
min(g.connectivity())
|
|
@@ -801,7 +843,7 @@ class PGraph(ABC):
|
|
|
801
843
|
|
|
802
844
|
This is a diagonal matrix where element ``[i,i]`` is the number
|
|
803
845
|
of edges connected to vertex id ``i``.
|
|
804
|
-
|
|
846
|
+
|
|
805
847
|
:seealso: :meth:`adjacency` :meth:`incidence` :meth:`laplacian`
|
|
806
848
|
"""
|
|
807
849
|
|
|
@@ -897,8 +939,6 @@ class PGraph(ABC):
|
|
|
897
939
|
|
|
898
940
|
# GRAPH COMPONENTS
|
|
899
941
|
|
|
900
|
-
|
|
901
|
-
|
|
902
942
|
def component(self, c):
|
|
903
943
|
"""
|
|
904
944
|
All vertices in specified graph component
|
|
@@ -920,7 +960,7 @@ class PGraph(ABC):
|
|
|
920
960
|
:rtype: bool
|
|
921
961
|
|
|
922
962
|
Test whether vertices belong to the same component. For a:
|
|
923
|
-
|
|
963
|
+
|
|
924
964
|
- directed graph this implies a path between them
|
|
925
965
|
- undirected graph there is not necessarily a path between them
|
|
926
966
|
"""
|
|
@@ -943,7 +983,7 @@ class PGraph(ABC):
|
|
|
943
983
|
# break
|
|
944
984
|
|
|
945
985
|
# v._edgelist = [] # remove all references to edges
|
|
946
|
-
# --------------------------------------------------------------------------- #
|
|
986
|
+
# --------------------------------------------------------------------------- #
|
|
947
987
|
|
|
948
988
|
def path_BFS(self, S, G, verbose=False, summary=False):
|
|
949
989
|
"""
|
|
@@ -963,11 +1003,11 @@ class PGraph(ABC):
|
|
|
963
1003
|
if isinstance(S, str):
|
|
964
1004
|
S = self[S]
|
|
965
1005
|
elif not isinstance(S, Vertex):
|
|
966
|
-
raise TypeError(
|
|
1006
|
+
raise TypeError("start must be Vertex subclass or string name")
|
|
967
1007
|
if isinstance(G, str):
|
|
968
1008
|
G = self[G]
|
|
969
1009
|
elif not isinstance(S, Vertex):
|
|
970
|
-
raise TypeError(
|
|
1010
|
+
raise TypeError("goal must be Vertex subclass or string name")
|
|
971
1011
|
|
|
972
1012
|
# we use lists not sets since the order is instructive in verbose
|
|
973
1013
|
# mode, really need ordered sets...
|
|
@@ -979,18 +1019,18 @@ class PGraph(ABC):
|
|
|
979
1019
|
while frontier:
|
|
980
1020
|
if verbose:
|
|
981
1021
|
print()
|
|
982
|
-
print(
|
|
983
|
-
print(
|
|
1022
|
+
print("FRONTIER:", ", ".join([v.name for v in frontier]))
|
|
1023
|
+
print("EXPLORED:", ", ".join([v.name for v in explored]))
|
|
984
1024
|
|
|
985
1025
|
x = frontier.pop(0)
|
|
986
1026
|
if verbose:
|
|
987
|
-
print(
|
|
1027
|
+
print(" expand", x.name)
|
|
988
1028
|
|
|
989
1029
|
# expand the vertex
|
|
990
1030
|
for n in x.neighbours():
|
|
991
1031
|
if n is G:
|
|
992
1032
|
if verbose:
|
|
993
|
-
print(
|
|
1033
|
+
print(" goal", n.name, "reached")
|
|
994
1034
|
parent[n] = x
|
|
995
1035
|
done = True
|
|
996
1036
|
break
|
|
@@ -998,13 +1038,13 @@ class PGraph(ABC):
|
|
|
998
1038
|
# add it to the frontier
|
|
999
1039
|
frontier.append(n)
|
|
1000
1040
|
if verbose:
|
|
1001
|
-
print(
|
|
1041
|
+
print(" add", n.name, "to the frontier")
|
|
1002
1042
|
parent[n] = x
|
|
1003
1043
|
if done:
|
|
1004
1044
|
break
|
|
1005
1045
|
explored.append(x)
|
|
1006
1046
|
if verbose:
|
|
1007
|
-
print(
|
|
1047
|
+
print(" move", x.name, "to the explored list")
|
|
1008
1048
|
else:
|
|
1009
1049
|
# no path
|
|
1010
1050
|
return None
|
|
@@ -1022,7 +1062,8 @@ class PGraph(ABC):
|
|
|
1022
1062
|
|
|
1023
1063
|
if summary or verbose:
|
|
1024
1064
|
print(
|
|
1025
|
-
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1065
|
+
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1066
|
+
)
|
|
1026
1067
|
|
|
1027
1068
|
return path, length
|
|
1028
1069
|
|
|
@@ -1048,11 +1089,11 @@ class PGraph(ABC):
|
|
|
1048
1089
|
if isinstance(S, str):
|
|
1049
1090
|
S = self[S]
|
|
1050
1091
|
elif not isinstance(S, Vertex):
|
|
1051
|
-
raise TypeError(
|
|
1092
|
+
raise TypeError("start must be Vertex subclass or string name")
|
|
1052
1093
|
if isinstance(G, str):
|
|
1053
1094
|
G = self[G]
|
|
1054
1095
|
elif not isinstance(S, Vertex):
|
|
1055
|
-
raise TypeError(
|
|
1096
|
+
raise TypeError("goal must be Vertex subclass or string name")
|
|
1056
1097
|
|
|
1057
1098
|
frontier = [S]
|
|
1058
1099
|
explored = []
|
|
@@ -1062,14 +1103,15 @@ class PGraph(ABC):
|
|
|
1062
1103
|
while frontier:
|
|
1063
1104
|
if verbose:
|
|
1064
1105
|
print()
|
|
1065
|
-
print(
|
|
1066
|
-
[f"{v.name}({f[v]:.0f})" for v in frontier])
|
|
1067
|
-
|
|
1106
|
+
print(
|
|
1107
|
+
"FRONTIER:", ", ".join([f"{v.name}({f[v]:.0f})" for v in frontier])
|
|
1108
|
+
)
|
|
1109
|
+
print("EXPLORED:", ", ".join([v.name for v in explored]))
|
|
1068
1110
|
|
|
1069
1111
|
i = np.argmin([f[n] for n in frontier]) # minimum f in frontier
|
|
1070
1112
|
x = frontier.pop(i)
|
|
1071
1113
|
if verbose:
|
|
1072
|
-
print(
|
|
1114
|
+
print(" expand", x.name)
|
|
1073
1115
|
if x is G:
|
|
1074
1116
|
break
|
|
1075
1117
|
# expand the vertex
|
|
@@ -1081,7 +1123,7 @@ class PGraph(ABC):
|
|
|
1081
1123
|
f[n] = fnew
|
|
1082
1124
|
frontier.append(n)
|
|
1083
1125
|
if verbose:
|
|
1084
|
-
print(
|
|
1126
|
+
print(" add", n.name, "to the frontier")
|
|
1085
1127
|
|
|
1086
1128
|
elif n in frontier:
|
|
1087
1129
|
# neighbour is already in the frontier
|
|
@@ -1089,13 +1131,14 @@ class PGraph(ABC):
|
|
|
1089
1131
|
if fnew < f[n]:
|
|
1090
1132
|
if verbose:
|
|
1091
1133
|
print(
|
|
1092
|
-
f" reparent {n.name}: cost {fnew} via {x.name} is less than cost {f[n]} via {parent[n].name}, change parent from {parent[n].name} to {x.name} "
|
|
1134
|
+
f" reparent {n.name}: cost {fnew} via {x.name} is less than cost {f[n]} via {parent[n].name}, change parent from {parent[n].name} to {x.name} "
|
|
1135
|
+
)
|
|
1093
1136
|
f[n] = fnew
|
|
1094
1137
|
parent[n] = x
|
|
1095
1138
|
|
|
1096
1139
|
explored.append(x)
|
|
1097
1140
|
if verbose:
|
|
1098
|
-
print(
|
|
1141
|
+
print(" move", x.name, "to the explored list")
|
|
1099
1142
|
else:
|
|
1100
1143
|
# no path
|
|
1101
1144
|
return None
|
|
@@ -1117,7 +1160,8 @@ class PGraph(ABC):
|
|
|
1117
1160
|
|
|
1118
1161
|
if summary or verbose:
|
|
1119
1162
|
print(
|
|
1120
|
-
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1163
|
+
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1164
|
+
)
|
|
1121
1165
|
|
|
1122
1166
|
return path, length, parent_names
|
|
1123
1167
|
|
|
@@ -1145,11 +1189,11 @@ class PGraph(ABC):
|
|
|
1145
1189
|
if isinstance(S, str):
|
|
1146
1190
|
S = self[S]
|
|
1147
1191
|
elif not isinstance(S, Vertex):
|
|
1148
|
-
raise TypeError(
|
|
1192
|
+
raise TypeError("start must be Vertex subclass or string name")
|
|
1149
1193
|
if isinstance(G, str):
|
|
1150
1194
|
G = self[G]
|
|
1151
1195
|
elif not isinstance(S, Vertex):
|
|
1152
|
-
raise TypeError(
|
|
1196
|
+
raise TypeError("goal must be Vertex subclass or string name")
|
|
1153
1197
|
|
|
1154
1198
|
frontier = [S]
|
|
1155
1199
|
explored = []
|
|
@@ -1160,14 +1204,15 @@ class PGraph(ABC):
|
|
|
1160
1204
|
while frontier:
|
|
1161
1205
|
if verbose:
|
|
1162
1206
|
print()
|
|
1163
|
-
print(
|
|
1164
|
-
[f"{v.name}({f[v]:.0f})" for v in frontier])
|
|
1165
|
-
|
|
1207
|
+
print(
|
|
1208
|
+
"FRONTIER:", ", ".join([f"{v.name}({f[v]:.0f})" for v in frontier])
|
|
1209
|
+
)
|
|
1210
|
+
print("EXPLORED:", ", ".join([v.name for v in explored]))
|
|
1166
1211
|
|
|
1167
1212
|
i = np.argmin([f[n] for n in frontier]) # minimum f in frontier
|
|
1168
1213
|
x = frontier.pop(i)
|
|
1169
1214
|
if verbose:
|
|
1170
|
-
print(
|
|
1215
|
+
print(" expand", x.name)
|
|
1171
1216
|
if x is G:
|
|
1172
1217
|
break
|
|
1173
1218
|
# expand the vertex
|
|
@@ -1179,7 +1224,7 @@ class PGraph(ABC):
|
|
|
1179
1224
|
g[n] = g[x] + e.cost # update cost to come
|
|
1180
1225
|
f[n] = g[n] + n.heuristic_distance(G) # heuristic
|
|
1181
1226
|
if verbose:
|
|
1182
|
-
print(
|
|
1227
|
+
print(" add", n.name, "to the frontier")
|
|
1183
1228
|
elif n in frontier:
|
|
1184
1229
|
# neighbour is already in the frontier
|
|
1185
1230
|
gnew = g[x] + e.cost
|
|
@@ -1187,7 +1232,8 @@ class PGraph(ABC):
|
|
|
1187
1232
|
# cost of path via x is lower that previous, reparent it
|
|
1188
1233
|
if verbose:
|
|
1189
1234
|
print(
|
|
1190
|
-
f" reparent {n.name}: cost {gnew} via {x.name} is less than cost {g[n]} via {parent[n].name}, change parent from {parent[n].name} to {x.name} "
|
|
1235
|
+
f" reparent {n.name}: cost {gnew} via {x.name} is less than cost {g[n]} via {parent[n].name}, change parent from {parent[n].name} to {x.name} "
|
|
1236
|
+
)
|
|
1191
1237
|
g[n] = gnew
|
|
1192
1238
|
f[n] = g[n] + n.heuristic_distance(G) # heuristic
|
|
1193
1239
|
|
|
@@ -1195,7 +1241,7 @@ class PGraph(ABC):
|
|
|
1195
1241
|
|
|
1196
1242
|
explored.append(x)
|
|
1197
1243
|
if verbose:
|
|
1198
|
-
print(
|
|
1244
|
+
print(" move", x.name, "to the explored list")
|
|
1199
1245
|
|
|
1200
1246
|
else:
|
|
1201
1247
|
# no path
|
|
@@ -1218,13 +1264,15 @@ class PGraph(ABC):
|
|
|
1218
1264
|
|
|
1219
1265
|
if summary or verbose:
|
|
1220
1266
|
print(
|
|
1221
|
-
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1267
|
+
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1268
|
+
)
|
|
1222
1269
|
|
|
1223
1270
|
return path, length, parent_names
|
|
1224
1271
|
|
|
1225
1272
|
|
|
1226
1273
|
# -------------------------------------------------------------------------- #
|
|
1227
1274
|
|
|
1275
|
+
|
|
1228
1276
|
class UGraph(PGraph):
|
|
1229
1277
|
"""
|
|
1230
1278
|
Class for undirected graphs
|
|
@@ -1263,7 +1311,7 @@ class UGraph(PGraph):
|
|
|
1263
1311
|
"""
|
|
1264
1312
|
Color the graph
|
|
1265
1313
|
|
|
1266
|
-
Performs a depth-first labeling operation, assigning the ``label``
|
|
1314
|
+
Performs a depth-first labeling operation, assigning the ``label``
|
|
1267
1315
|
attribute of every vertex with a sequential integer starting from 0.
|
|
1268
1316
|
|
|
1269
1317
|
This method checks the ``_connectivitychange`` attribute of all vertices
|
|
@@ -1281,7 +1329,6 @@ class UGraph(PGraph):
|
|
|
1281
1329
|
vertex.label = None
|
|
1282
1330
|
vertex._connectivitychange = False
|
|
1283
1331
|
|
|
1284
|
-
|
|
1285
1332
|
lastlabel = None
|
|
1286
1333
|
for label in range(self.n):
|
|
1287
1334
|
assignment = False
|
|
@@ -1304,6 +1351,7 @@ class UGraph(PGraph):
|
|
|
1304
1351
|
|
|
1305
1352
|
self._ncomponents = lastlabel + 1
|
|
1306
1353
|
|
|
1354
|
+
|
|
1307
1355
|
class DGraph(PGraph):
|
|
1308
1356
|
"""
|
|
1309
1357
|
Class for directed graphs
|
|
@@ -1342,7 +1390,7 @@ class DGraph(PGraph):
|
|
|
1342
1390
|
"""
|
|
1343
1391
|
Color the graph
|
|
1344
1392
|
|
|
1345
|
-
Performs a depth-first labeling operation, assigning the ``label``
|
|
1393
|
+
Performs a depth-first labeling operation, assigning the ``label``
|
|
1346
1394
|
attribute of every vertex with a sequential integer starting from 0.
|
|
1347
1395
|
|
|
1348
1396
|
This method checks the ``_connectivitychange`` attribute of all vertices
|
|
@@ -1397,10 +1445,15 @@ class DGraph(PGraph):
|
|
|
1397
1445
|
for v in self:
|
|
1398
1446
|
v.label = final[v.label]
|
|
1399
1447
|
|
|
1400
|
-
|
|
1448
|
+
return len(unique)
|
|
1449
|
+
else:
|
|
1450
|
+
# no coloring performed
|
|
1451
|
+
return None
|
|
1452
|
+
|
|
1401
1453
|
|
|
1402
1454
|
# ========================================================================== #
|
|
1403
1455
|
|
|
1456
|
+
|
|
1404
1457
|
class Edge:
|
|
1405
1458
|
"""
|
|
1406
1459
|
Edge class
|
|
@@ -1415,7 +1468,7 @@ class Edge:
|
|
|
1415
1468
|
|
|
1416
1469
|
.. note::
|
|
1417
1470
|
|
|
1418
|
-
- An undirected graph is created by having a single edge object in the
|
|
1471
|
+
- An undirected graph is created by having a single edge object in the
|
|
1419
1472
|
edgelist of _each_ vertex.
|
|
1420
1473
|
- This class can be inherited to provide user objects with graph capability.
|
|
1421
1474
|
- Inheritance is an alternative to providing arbitrary user data.
|
|
@@ -1468,7 +1521,6 @@ class Edge:
|
|
|
1468
1521
|
else:
|
|
1469
1522
|
self.cost = None
|
|
1470
1523
|
|
|
1471
|
-
|
|
1472
1524
|
def __repr__(self):
|
|
1473
1525
|
return str(self)
|
|
1474
1526
|
|
|
@@ -1489,16 +1541,16 @@ class Edge:
|
|
|
1489
1541
|
:type v2: Vertex subclass
|
|
1490
1542
|
|
|
1491
1543
|
The edge is added to the graph and connects vertices ``v1`` and ``v2``.
|
|
1492
|
-
|
|
1544
|
+
|
|
1493
1545
|
.. note:: The vertices must already be added to the graph.
|
|
1494
1546
|
"""
|
|
1495
1547
|
|
|
1496
1548
|
if v1._graph is None:
|
|
1497
|
-
raise ValueError(
|
|
1549
|
+
raise ValueError("vertex v1 does not belong to a graph")
|
|
1498
1550
|
if v2._graph is None:
|
|
1499
|
-
raise ValueError(
|
|
1551
|
+
raise ValueError("vertex v2 does not belong to a graph")
|
|
1500
1552
|
if not v1._graph is v2._graph:
|
|
1501
|
-
raise ValueError(
|
|
1553
|
+
raise ValueError("vertices must belong to the same graph")
|
|
1502
1554
|
|
|
1503
1555
|
# connect edge to its vertices
|
|
1504
1556
|
self.v1 = v1
|
|
@@ -1508,7 +1560,6 @@ class Edge:
|
|
|
1508
1560
|
# DGraph or UGraph
|
|
1509
1561
|
v1.connect(v2, edge=self)
|
|
1510
1562
|
|
|
1511
|
-
|
|
1512
1563
|
def next(self, vertex):
|
|
1513
1564
|
"""
|
|
1514
1565
|
Return other end of an edge
|
|
@@ -1519,7 +1570,7 @@ class Edge:
|
|
|
1519
1570
|
:return: the other vertex on the edge
|
|
1520
1571
|
:rtype: Vertex subclass
|
|
1521
1572
|
|
|
1522
|
-
``e.next(v1)`` is the vertex at the other end of edge ``e``, ie. the
|
|
1573
|
+
``e.next(v1)`` is the vertex at the other end of edge ``e``, ie. the
|
|
1523
1574
|
vertex that is not ``v1``.
|
|
1524
1575
|
"""
|
|
1525
1576
|
|
|
@@ -1528,10 +1579,10 @@ class Edge:
|
|
|
1528
1579
|
elif self.v2 is vertex:
|
|
1529
1580
|
return self.v1
|
|
1530
1581
|
else:
|
|
1531
|
-
raise ValueError(
|
|
1582
|
+
raise ValueError("shouldnt happen")
|
|
1532
1583
|
|
|
1533
1584
|
def vertices(self):
|
|
1534
|
-
raise DeprecationWarning(
|
|
1585
|
+
raise DeprecationWarning("use endpoints instead")
|
|
1535
1586
|
|
|
1536
1587
|
@property
|
|
1537
1588
|
def endpoints(self):
|
|
@@ -1558,6 +1609,7 @@ class Edge:
|
|
|
1558
1609
|
# self.v1 = None
|
|
1559
1610
|
# self.v2 = None
|
|
1560
1611
|
|
|
1612
|
+
|
|
1561
1613
|
# ========================================================================== #
|
|
1562
1614
|
|
|
1563
1615
|
|
|
@@ -1590,9 +1642,9 @@ class Vertex:
|
|
|
1590
1642
|
|
|
1591
1643
|
def __repr__(self):
|
|
1592
1644
|
if self.coord is None:
|
|
1593
|
-
coord =
|
|
1645
|
+
coord = "?"
|
|
1594
1646
|
else:
|
|
1595
|
-
coord =
|
|
1647
|
+
coord = ", ".join([f"{x:.4g}" for x in self.coord])
|
|
1596
1648
|
return f"{self.__class__.__name__}[{self.name:s}, coord=({coord})]"
|
|
1597
1649
|
|
|
1598
1650
|
def copy(self, cls=None):
|
|
@@ -1601,32 +1653,15 @@ class Vertex:
|
|
|
1601
1653
|
else:
|
|
1602
1654
|
return self.__class__(coord=self.coord, name=self.name)
|
|
1603
1655
|
|
|
1604
|
-
|
|
1605
|
-
def adjacent(self):
|
|
1606
|
-
"""
|
|
1607
|
-
Adjacent vertices
|
|
1608
|
-
|
|
1609
|
-
``v.adjacent()`` is a list of vertices adjacent to this vertex.
|
|
1610
|
-
|
|
1611
|
-
:note: For a directed graph the neighbours are those on edges leaving this vertex
|
|
1612
|
-
|
|
1613
|
-
:seealso: :meth:`neighbours` :meth:`neighbors`
|
|
1614
|
-
"""
|
|
1615
|
-
return [e.next(self) for e in self._edgelist]
|
|
1616
|
-
|
|
1617
1656
|
def neighbours(self):
|
|
1618
1657
|
"""
|
|
1619
1658
|
Neighbours of a vertex
|
|
1620
1659
|
|
|
1621
1660
|
``v.neighbours()`` is a list of neighbours of this vertex.
|
|
1622
1661
|
|
|
1623
|
-
.. note::
|
|
1624
|
-
- For a directed graph the neighbours are those on edges leaving this vertex
|
|
1625
|
-
- British English synonym for :meth:`adjacent`
|
|
1626
|
-
|
|
1627
|
-
:seealso: :meth:`adjacent` :meth:`neighbours`
|
|
1662
|
+
.. note:: For a directed graph the neighbours are those on edges leaving this vertex
|
|
1628
1663
|
"""
|
|
1629
|
-
return
|
|
1664
|
+
return [e.next(self) for e in self._edgelist]
|
|
1630
1665
|
|
|
1631
1666
|
def neighbors(self):
|
|
1632
1667
|
"""
|
|
@@ -1634,14 +1669,9 @@ class Vertex:
|
|
|
1634
1669
|
|
|
1635
1670
|
``v.neighbors()`` is a list of neighbors of this vertex.
|
|
1636
1671
|
|
|
1637
|
-
.. note::
|
|
1638
|
-
- For a directed graph the neighbors are those on edges leaving this vertex
|
|
1639
|
-
- US English synonym for :meth:`adjacent`
|
|
1640
|
-
|
|
1641
|
-
:seealso: :meth:`adjacent` :meth:`neighbours`
|
|
1672
|
+
.. note:: For a directed graph the neighbours are those on edges leaving this vertex
|
|
1642
1673
|
"""
|
|
1643
|
-
return
|
|
1644
|
-
|
|
1674
|
+
return [e.next(self) for e in self._edgelist]
|
|
1645
1675
|
|
|
1646
1676
|
def isneighbour(self, vertex):
|
|
1647
1677
|
"""
|
|
@@ -1661,7 +1691,7 @@ class Vertex:
|
|
|
1661
1691
|
"""
|
|
1662
1692
|
Neighbours and edges of a vertex
|
|
1663
1693
|
|
|
1664
|
-
``v.incidences()`` is a generator that returns a list of incidences,
|
|
1694
|
+
``v.incidences()`` is a generator that returns a list of incidences,
|
|
1665
1695
|
tuples of (vertex, edge) for all neighbours of the vertex ``v``.
|
|
1666
1696
|
|
|
1667
1697
|
.. note:: For a directed graph the edges are those leaving this vertex
|
|
@@ -1699,7 +1729,7 @@ class Vertex:
|
|
|
1699
1729
|
"""
|
|
1700
1730
|
|
|
1701
1731
|
if not dest.__class__.__bases__[0] is self.__class__.__bases__[0]:
|
|
1702
|
-
raise TypeError(
|
|
1732
|
+
raise TypeError("must connect vertices of same type")
|
|
1703
1733
|
elif isinstance(edge, Edge):
|
|
1704
1734
|
e = edge
|
|
1705
1735
|
else:
|
|
@@ -1725,10 +1755,10 @@ class Vertex:
|
|
|
1725
1755
|
|
|
1726
1756
|
- For a directed graph ``dest`` must be at the arrow end of the edge
|
|
1727
1757
|
"""
|
|
1728
|
-
for
|
|
1758
|
+
for n, e in self.incidences():
|
|
1729
1759
|
if n is dest:
|
|
1730
1760
|
return e
|
|
1731
|
-
raise ValueError(
|
|
1761
|
+
raise ValueError("dest is not a neighbour")
|
|
1732
1762
|
|
|
1733
1763
|
def edges(self):
|
|
1734
1764
|
"""
|
|
@@ -1814,6 +1844,7 @@ class Vertex:
|
|
|
1814
1844
|
def closest(self):
|
|
1815
1845
|
return self._graph.closest(self.coord)
|
|
1816
1846
|
|
|
1847
|
+
|
|
1817
1848
|
class UVertex(Vertex):
|
|
1818
1849
|
"""
|
|
1819
1850
|
Vertex subclass for undirected graphs
|
|
@@ -1832,7 +1863,7 @@ class UVertex(Vertex):
|
|
|
1832
1863
|
elif isinstance(other, Edge):
|
|
1833
1864
|
e = super().connect(edge=other)
|
|
1834
1865
|
else:
|
|
1835
|
-
raise TypeError(
|
|
1866
|
+
raise TypeError("bad argument")
|
|
1836
1867
|
|
|
1837
1868
|
# e = super().connect(other, **kwargs)
|
|
1838
1869
|
|
|
@@ -1859,7 +1890,7 @@ class DVertex(Vertex):
|
|
|
1859
1890
|
elif isinstance(other, Edge):
|
|
1860
1891
|
e = super().connect(edge=other)
|
|
1861
1892
|
else:
|
|
1862
|
-
raise TypeError(
|
|
1893
|
+
raise TypeError("bad argument")
|
|
1863
1894
|
|
|
1864
1895
|
self._edgelist.append(e)
|
|
1865
1896
|
return e
|
|
@@ -1871,10 +1902,11 @@ class DVertex(Vertex):
|
|
|
1871
1902
|
if __name__ == "__main__":
|
|
1872
1903
|
|
|
1873
1904
|
g = UGraph()
|
|
1905
|
+
print(g)
|
|
1874
1906
|
|
|
1875
1907
|
for i in range(10):
|
|
1876
1908
|
g.add_vertex()
|
|
1877
1909
|
|
|
1878
1910
|
g.add_edge(g[0], g[1])
|
|
1879
1911
|
|
|
1880
|
-
print(g)
|
|
1912
|
+
print(g)
|