pgraph-python 0.6.2__py3-none-any.whl → 0.6.3__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 +171 -147
- {pgraph_python-0.6.2.dist-info → pgraph_python-0.6.3.dist-info}/METADATA +35 -26
- pgraph_python-0.6.3.dist-info/RECORD +7 -0
- {pgraph_python-0.6.2.dist-info → pgraph_python-0.6.3.dist-info}/WHEEL +1 -1
- {pgraph_python-0.6.2.dist-info → pgraph_python-0.6.3.dist-info}/top_level.txt +0 -1
- pgraph_python-0.6.2.dist-info/RECORD +0 -9
- tests/__init__.py +0 -0
- tests/test_graph.py +0 -737
- {pgraph_python-0.6.2.dist-info → pgraph_python-0.6.3.dist-info}/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,9 +302,9 @@ 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
310
|
self._graphcolor()
|
|
@@ -317,7 +317,7 @@ class PGraph(ABC):
|
|
|
317
317
|
|
|
318
318
|
def L2(v):
|
|
319
319
|
return np.linalg.norm(v)
|
|
320
|
-
|
|
320
|
+
|
|
321
321
|
def SE2(v):
|
|
322
322
|
# wrap angle to range [-pi, pi)
|
|
323
323
|
v[2] = (v[2] + np.pi) % (2 * np.pi) - np.pi
|
|
@@ -326,14 +326,14 @@ class PGraph(ABC):
|
|
|
326
326
|
if callable(metric):
|
|
327
327
|
return metric
|
|
328
328
|
elif isinstance(metric, str):
|
|
329
|
-
if metric ==
|
|
329
|
+
if metric == "L1":
|
|
330
330
|
return L1
|
|
331
|
-
elif metric ==
|
|
331
|
+
elif metric == "L2":
|
|
332
332
|
return L2
|
|
333
|
-
elif metric ==
|
|
333
|
+
elif metric == "SE2":
|
|
334
334
|
return SE2
|
|
335
335
|
else:
|
|
336
|
-
raise ValueError(
|
|
336
|
+
raise ValueError("unknown metric")
|
|
337
337
|
|
|
338
338
|
@property
|
|
339
339
|
def metric(self):
|
|
@@ -355,7 +355,7 @@ class PGraph(ABC):
|
|
|
355
355
|
:param metric: distance metric
|
|
356
356
|
:type metric: callable or str
|
|
357
357
|
|
|
358
|
-
This is a function of a vector and returns a scalar. It can be
|
|
358
|
+
This is a function of a vector and returns a scalar. It can be
|
|
359
359
|
user defined function or a string:
|
|
360
360
|
|
|
361
361
|
- 'L1' is the norm :math:`L_1 = \Sigma_i | v_i |`
|
|
@@ -363,7 +363,7 @@ class PGraph(ABC):
|
|
|
363
363
|
- 'SE2' is a mixed norm for vectors :math:`(x, y, \theta)` and
|
|
364
364
|
is :math:`\sqrt{x^2 + y^2 + \bar{\theta}^2}` where :math:`\bar{\theta}`
|
|
365
365
|
is :math:`\theta` wrapped to the interval :math:`[-\pi, \pi)`
|
|
366
|
-
|
|
366
|
+
|
|
367
367
|
The metric is used by :meth:`closest` and :meth:`distance`
|
|
368
368
|
"""
|
|
369
369
|
self._metric = self._metricfunc(metric)
|
|
@@ -388,7 +388,7 @@ class PGraph(ABC):
|
|
|
388
388
|
:param metric: heuristic distance metric
|
|
389
389
|
:type metric: callable or str
|
|
390
390
|
|
|
391
|
-
This is a function of a vector and returns a scalar. It can be
|
|
391
|
+
This is a function of a vector and returns a scalar. It can be
|
|
392
392
|
user defined function or a string:
|
|
393
393
|
|
|
394
394
|
- 'L1' is the norm :math:`L_1 = \Sigma_i | v_i |`
|
|
@@ -408,7 +408,7 @@ class PGraph(ABC):
|
|
|
408
408
|
if vertex.label is not None:
|
|
409
409
|
ss += " component={vertex.label}"
|
|
410
410
|
s.append(ss)
|
|
411
|
-
return
|
|
411
|
+
return "\n".join(s)
|
|
412
412
|
|
|
413
413
|
def __getitem__(self, i):
|
|
414
414
|
"""
|
|
@@ -421,7 +421,7 @@ class PGraph(ABC):
|
|
|
421
421
|
|
|
422
422
|
Retrieve a vertex by index or name:
|
|
423
423
|
|
|
424
|
-
-``g[i]`` is the i'th vertex in the graph. This reflects the order of
|
|
424
|
+
-``g[i]`` is the i'th vertex in the graph. This reflects the order of
|
|
425
425
|
addition to the graph.
|
|
426
426
|
-``g[s]`` is vertex named ``s``
|
|
427
427
|
-``g[v]`` is ``v`` where ``v`` is a ``Vertex`` subclass
|
|
@@ -474,7 +474,7 @@ class PGraph(ABC):
|
|
|
474
474
|
|
|
475
475
|
:seealso: :meth:`metric`
|
|
476
476
|
"""
|
|
477
|
-
min_dist = np.
|
|
477
|
+
min_dist = np.inf
|
|
478
478
|
min_which = None
|
|
479
479
|
|
|
480
480
|
for vertex in self:
|
|
@@ -482,7 +482,7 @@ class PGraph(ABC):
|
|
|
482
482
|
if d < min_dist:
|
|
483
483
|
min_dist = d
|
|
484
484
|
min_which = vertex
|
|
485
|
-
|
|
485
|
+
|
|
486
486
|
return min_which, min_dist
|
|
487
487
|
|
|
488
488
|
def edges(self):
|
|
@@ -504,7 +504,17 @@ class PGraph(ABC):
|
|
|
504
504
|
"""
|
|
505
505
|
return self._edgelist
|
|
506
506
|
|
|
507
|
-
def plot(
|
|
507
|
+
def plot(
|
|
508
|
+
self,
|
|
509
|
+
colorcomponents=True,
|
|
510
|
+
force2d=False,
|
|
511
|
+
vopt={},
|
|
512
|
+
eopt={},
|
|
513
|
+
text={},
|
|
514
|
+
block=False,
|
|
515
|
+
grid=True,
|
|
516
|
+
ax=None,
|
|
517
|
+
):
|
|
508
518
|
"""
|
|
509
519
|
Plot the graph
|
|
510
520
|
|
|
@@ -528,10 +538,9 @@ class PGraph(ABC):
|
|
|
528
538
|
which are the vertex names. If ``text`` is None default formatting is
|
|
529
539
|
used. If ``text`` is False no labels are added.
|
|
530
540
|
"""
|
|
531
|
-
vopt = {**dict(marker=
|
|
541
|
+
vopt = {**dict(marker="o", markersize=12), **vopt}
|
|
532
542
|
eopt = {**dict(linewidth=3), **eopt}
|
|
533
543
|
|
|
534
|
-
|
|
535
544
|
if colorcomponents:
|
|
536
545
|
color = plt.cm.coolwarm(np.linspace(0, 1, self.nc))
|
|
537
546
|
|
|
@@ -547,8 +556,12 @@ class PGraph(ABC):
|
|
|
547
556
|
if colorcomponents:
|
|
548
557
|
ax.plot(vertex.x, vertex.y, color=color[c, :], **vopt)
|
|
549
558
|
for v in vertex.neighbours():
|
|
550
|
-
ax.plot(
|
|
551
|
-
|
|
559
|
+
ax.plot(
|
|
560
|
+
[vertex.x, v.x],
|
|
561
|
+
[vertex.y, v.y],
|
|
562
|
+
color=color[c, :],
|
|
563
|
+
**eopt,
|
|
564
|
+
)
|
|
552
565
|
else:
|
|
553
566
|
ax.plot(vertex.x, vertex.y, **vopt)
|
|
554
567
|
for v in vertex.neighbours():
|
|
@@ -561,22 +574,37 @@ class PGraph(ABC):
|
|
|
561
574
|
# for each component
|
|
562
575
|
for vertex in self.component(c):
|
|
563
576
|
if text is not False:
|
|
564
|
-
ax.text(
|
|
577
|
+
ax.text(
|
|
578
|
+
vertex.x, vertex.y, vertex.z, " " + vertex.name, **text
|
|
579
|
+
)
|
|
565
580
|
if colorcomponents:
|
|
566
|
-
ax.plot(
|
|
581
|
+
ax.plot(
|
|
582
|
+
vertex.x,
|
|
583
|
+
vertex.y,
|
|
584
|
+
vertex.z,
|
|
585
|
+
**{**dict(color=color[c, :]), **vopt},
|
|
586
|
+
)
|
|
567
587
|
for v in vertex.neighbours():
|
|
568
|
-
ax.plot(
|
|
569
|
-
|
|
588
|
+
ax.plot(
|
|
589
|
+
[vertex.x, v.x],
|
|
590
|
+
[vertex.y, v.y],
|
|
591
|
+
[vertex.z, v.z],
|
|
592
|
+
**{**dict(color=color[c, :]), **eopt},
|
|
593
|
+
)
|
|
570
594
|
else:
|
|
571
595
|
ax.plot(vertex.x, vertex.y, **vopt)
|
|
572
596
|
for v in vertex.neighbours():
|
|
573
|
-
ax.plot(
|
|
597
|
+
ax.plot(
|
|
598
|
+
[vertex.x, v.x],
|
|
599
|
+
[vertex.y, v.y],
|
|
600
|
+
[vertex.z, v.z],
|
|
601
|
+
**eopt,
|
|
602
|
+
)
|
|
574
603
|
# if nc > 1:
|
|
575
604
|
# # add a colorbar
|
|
576
605
|
# plt.colorbar()
|
|
577
606
|
ax.grid(grid)
|
|
578
|
-
|
|
579
|
-
plt.show(block=block)
|
|
607
|
+
plt.show(block=block)
|
|
580
608
|
|
|
581
609
|
def highlight_path(self, path, block=False, **kwargs):
|
|
582
610
|
"""
|
|
@@ -594,12 +622,12 @@ class PGraph(ABC):
|
|
|
594
622
|
"""
|
|
595
623
|
for i in range(len(path)):
|
|
596
624
|
if i < len(path) - 1:
|
|
597
|
-
e = path[i].edgeto(path[i+1])
|
|
625
|
+
e = path[i].edgeto(path[i + 1])
|
|
598
626
|
self.highlight_edge(e, **kwargs)
|
|
599
627
|
self.highlight_vertex(path[i], **kwargs)
|
|
600
628
|
plt.show(block=block)
|
|
601
629
|
|
|
602
|
-
def highlight_edge(self, edge, scale=2, color=
|
|
630
|
+
def highlight_edge(self, edge, scale=2, color="r", alpha=0.5):
|
|
603
631
|
"""
|
|
604
632
|
Highlight an edge in the graph
|
|
605
633
|
|
|
@@ -613,10 +641,11 @@ class PGraph(ABC):
|
|
|
613
641
|
"""
|
|
614
642
|
p1 = edge.v1
|
|
615
643
|
p2 = edge.v2
|
|
616
|
-
plt.plot(
|
|
617
|
-
|
|
644
|
+
plt.plot(
|
|
645
|
+
[p1.x, p2.x], [p1.y, p2.y], color=color, linewidth=3 * scale, alpha=alpha
|
|
646
|
+
)
|
|
618
647
|
|
|
619
|
-
def highlight_vertex(self, vertex, scale=2, color=
|
|
648
|
+
def highlight_vertex(self, vertex, scale=2, color="r", alpha=0.5):
|
|
620
649
|
"""
|
|
621
650
|
Highlight a vertex in the graph
|
|
622
651
|
|
|
@@ -632,11 +661,11 @@ class PGraph(ABC):
|
|
|
632
661
|
for n in vertex:
|
|
633
662
|
if isinstance(n, str):
|
|
634
663
|
n = self[n]
|
|
635
|
-
plt.plot(n.x, n.y,
|
|
636
|
-
markersize=12 * scale, alpha=alpha)
|
|
664
|
+
plt.plot(n.x, n.y, "o", color=color, markersize=12 * scale, alpha=alpha)
|
|
637
665
|
else:
|
|
638
|
-
plt.plot(
|
|
639
|
-
|
|
666
|
+
plt.plot(
|
|
667
|
+
vertex.x, vertex.y, "o", color=color, markersize=12 * scale, alpha=alpha
|
|
668
|
+
)
|
|
640
669
|
|
|
641
670
|
def dotfile(self, filename=None, direction=None):
|
|
642
671
|
"""
|
|
@@ -645,15 +674,19 @@ class PGraph(ABC):
|
|
|
645
674
|
:param filename: filename to save graph to, defaults to None
|
|
646
675
|
:type filename: str, optional
|
|
647
676
|
|
|
648
|
-
``g.dotfile()`` creates the specified file which contains the
|
|
649
|
-
|
|
650
|
-
is to the console.
|
|
677
|
+
``g.dotfile()`` creates the specified file which contains the `GraphViz
|
|
678
|
+
<https://graphviz.org>`_ code to represent the embedded graph. By default
|
|
679
|
+
output is to the console.
|
|
651
680
|
|
|
652
681
|
.. note::
|
|
653
682
|
|
|
654
683
|
- The graph is undirected if it is a subclass of ``UGraph``
|
|
655
684
|
- The graph is directed if it is a subclass of ``DGraph``
|
|
656
|
-
|
|
685
|
+
|
|
686
|
+
The ``graphviz`` formatters ``dot`` and ``neato`` can be used to render the
|
|
687
|
+
graph in various formats including PDF, PNG, JPG, GIF, SVG, etc. The formmatters are
|
|
688
|
+
similar, but ``dot`` is better suited for directed graphs while ``neato`` is better
|
|
689
|
+
suited for undirected graphs.
|
|
657
690
|
|
|
658
691
|
.. note:: If ``filename`` is a file object then the file will *not*
|
|
659
692
|
be closed after the GraphViz model is written.
|
|
@@ -681,8 +714,12 @@ class PGraph(ABC):
|
|
|
681
714
|
if vertex.coord is None:
|
|
682
715
|
print(' "{:s}"'.format(vertex.name), file=f)
|
|
683
716
|
else:
|
|
684
|
-
print(
|
|
685
|
-
|
|
717
|
+
print(
|
|
718
|
+
' "{:s}" [pos="{:.5g},{:.5g}"]'.format(
|
|
719
|
+
vertex.name, vertex.coord[0], vertex.coord[1]
|
|
720
|
+
),
|
|
721
|
+
file=f,
|
|
722
|
+
)
|
|
686
723
|
print(file=f)
|
|
687
724
|
# add the edges
|
|
688
725
|
for e in self.edges():
|
|
@@ -691,7 +728,7 @@ class PGraph(ABC):
|
|
|
691
728
|
else:
|
|
692
729
|
print(' "{:s}" -- "{:s}"'.format(e.v1.name, e.v2.name), file=f)
|
|
693
730
|
|
|
694
|
-
print(
|
|
731
|
+
print("}", file=f)
|
|
695
732
|
|
|
696
733
|
if filename is None or isinstance(filename, str):
|
|
697
734
|
f.close() # noqa
|
|
@@ -734,7 +771,7 @@ class PGraph(ABC):
|
|
|
734
771
|
:return: average degree
|
|
735
772
|
:rtype: float
|
|
736
773
|
|
|
737
|
-
Average degree is :math:`2 E / N` for an undirected graph and
|
|
774
|
+
Average degree is :math:`2 E / N` for an undirected graph and
|
|
738
775
|
:math:`E / N` for a directed graph where :math:`E` is the total number of
|
|
739
776
|
edges and :math:`N` is the number of vertices.
|
|
740
777
|
|
|
@@ -744,7 +781,7 @@ class PGraph(ABC):
|
|
|
744
781
|
elif isinstance(self, UGraph):
|
|
745
782
|
return 2 * len(self.edges()) / self.n
|
|
746
783
|
|
|
747
|
-
# --------------------------------------------------------------------------- #
|
|
784
|
+
# --------------------------------------------------------------------------- #
|
|
748
785
|
|
|
749
786
|
# MATRIX REPRESENTATIONS
|
|
750
787
|
|
|
@@ -762,7 +799,7 @@ class PGraph(ABC):
|
|
|
762
799
|
|
|
763
800
|
- Laplacian is always positive-semidefinite.
|
|
764
801
|
- Laplacian has at least one zero eigenvalue.
|
|
765
|
-
- The number of zero-valued eigenvalues is the number of connected
|
|
802
|
+
- The number of zero-valued eigenvalues is the number of connected
|
|
766
803
|
components in the graph.
|
|
767
804
|
|
|
768
805
|
:seealso: :meth:`adjacency` :meth:`incidence` :meth:`degree`
|
|
@@ -779,7 +816,7 @@ class PGraph(ABC):
|
|
|
779
816
|
The average vertex connectivity is::
|
|
780
817
|
|
|
781
818
|
mean(g.connectivity())
|
|
782
|
-
|
|
819
|
+
|
|
783
820
|
and the minimum vertex connectivity is::
|
|
784
821
|
|
|
785
822
|
min(g.connectivity())
|
|
@@ -801,7 +838,7 @@ class PGraph(ABC):
|
|
|
801
838
|
|
|
802
839
|
This is a diagonal matrix where element ``[i,i]`` is the number
|
|
803
840
|
of edges connected to vertex id ``i``.
|
|
804
|
-
|
|
841
|
+
|
|
805
842
|
:seealso: :meth:`adjacency` :meth:`incidence` :meth:`laplacian`
|
|
806
843
|
"""
|
|
807
844
|
|
|
@@ -897,8 +934,6 @@ class PGraph(ABC):
|
|
|
897
934
|
|
|
898
935
|
# GRAPH COMPONENTS
|
|
899
936
|
|
|
900
|
-
|
|
901
|
-
|
|
902
937
|
def component(self, c):
|
|
903
938
|
"""
|
|
904
939
|
All vertices in specified graph component
|
|
@@ -920,7 +955,7 @@ class PGraph(ABC):
|
|
|
920
955
|
:rtype: bool
|
|
921
956
|
|
|
922
957
|
Test whether vertices belong to the same component. For a:
|
|
923
|
-
|
|
958
|
+
|
|
924
959
|
- directed graph this implies a path between them
|
|
925
960
|
- undirected graph there is not necessarily a path between them
|
|
926
961
|
"""
|
|
@@ -943,7 +978,7 @@ class PGraph(ABC):
|
|
|
943
978
|
# break
|
|
944
979
|
|
|
945
980
|
# v._edgelist = [] # remove all references to edges
|
|
946
|
-
# --------------------------------------------------------------------------- #
|
|
981
|
+
# --------------------------------------------------------------------------- #
|
|
947
982
|
|
|
948
983
|
def path_BFS(self, S, G, verbose=False, summary=False):
|
|
949
984
|
"""
|
|
@@ -963,11 +998,11 @@ class PGraph(ABC):
|
|
|
963
998
|
if isinstance(S, str):
|
|
964
999
|
S = self[S]
|
|
965
1000
|
elif not isinstance(S, Vertex):
|
|
966
|
-
raise TypeError(
|
|
1001
|
+
raise TypeError("start must be Vertex subclass or string name")
|
|
967
1002
|
if isinstance(G, str):
|
|
968
1003
|
G = self[G]
|
|
969
1004
|
elif not isinstance(S, Vertex):
|
|
970
|
-
raise TypeError(
|
|
1005
|
+
raise TypeError("goal must be Vertex subclass or string name")
|
|
971
1006
|
|
|
972
1007
|
# we use lists not sets since the order is instructive in verbose
|
|
973
1008
|
# mode, really need ordered sets...
|
|
@@ -979,18 +1014,18 @@ class PGraph(ABC):
|
|
|
979
1014
|
while frontier:
|
|
980
1015
|
if verbose:
|
|
981
1016
|
print()
|
|
982
|
-
print(
|
|
983
|
-
print(
|
|
1017
|
+
print("FRONTIER:", ", ".join([v.name for v in frontier]))
|
|
1018
|
+
print("EXPLORED:", ", ".join([v.name for v in explored]))
|
|
984
1019
|
|
|
985
1020
|
x = frontier.pop(0)
|
|
986
1021
|
if verbose:
|
|
987
|
-
print(
|
|
1022
|
+
print(" expand", x.name)
|
|
988
1023
|
|
|
989
1024
|
# expand the vertex
|
|
990
1025
|
for n in x.neighbours():
|
|
991
1026
|
if n is G:
|
|
992
1027
|
if verbose:
|
|
993
|
-
print(
|
|
1028
|
+
print(" goal", n.name, "reached")
|
|
994
1029
|
parent[n] = x
|
|
995
1030
|
done = True
|
|
996
1031
|
break
|
|
@@ -998,13 +1033,13 @@ class PGraph(ABC):
|
|
|
998
1033
|
# add it to the frontier
|
|
999
1034
|
frontier.append(n)
|
|
1000
1035
|
if verbose:
|
|
1001
|
-
print(
|
|
1036
|
+
print(" add", n.name, "to the frontier")
|
|
1002
1037
|
parent[n] = x
|
|
1003
1038
|
if done:
|
|
1004
1039
|
break
|
|
1005
1040
|
explored.append(x)
|
|
1006
1041
|
if verbose:
|
|
1007
|
-
print(
|
|
1042
|
+
print(" move", x.name, "to the explored list")
|
|
1008
1043
|
else:
|
|
1009
1044
|
# no path
|
|
1010
1045
|
return None
|
|
@@ -1022,7 +1057,8 @@ class PGraph(ABC):
|
|
|
1022
1057
|
|
|
1023
1058
|
if summary or verbose:
|
|
1024
1059
|
print(
|
|
1025
|
-
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1060
|
+
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1061
|
+
)
|
|
1026
1062
|
|
|
1027
1063
|
return path, length
|
|
1028
1064
|
|
|
@@ -1048,11 +1084,11 @@ class PGraph(ABC):
|
|
|
1048
1084
|
if isinstance(S, str):
|
|
1049
1085
|
S = self[S]
|
|
1050
1086
|
elif not isinstance(S, Vertex):
|
|
1051
|
-
raise TypeError(
|
|
1087
|
+
raise TypeError("start must be Vertex subclass or string name")
|
|
1052
1088
|
if isinstance(G, str):
|
|
1053
1089
|
G = self[G]
|
|
1054
1090
|
elif not isinstance(S, Vertex):
|
|
1055
|
-
raise TypeError(
|
|
1091
|
+
raise TypeError("goal must be Vertex subclass or string name")
|
|
1056
1092
|
|
|
1057
1093
|
frontier = [S]
|
|
1058
1094
|
explored = []
|
|
@@ -1062,14 +1098,15 @@ class PGraph(ABC):
|
|
|
1062
1098
|
while frontier:
|
|
1063
1099
|
if verbose:
|
|
1064
1100
|
print()
|
|
1065
|
-
print(
|
|
1066
|
-
[f"{v.name}({f[v]:.0f})" for v in frontier])
|
|
1067
|
-
|
|
1101
|
+
print(
|
|
1102
|
+
"FRONTIER:", ", ".join([f"{v.name}({f[v]:.0f})" for v in frontier])
|
|
1103
|
+
)
|
|
1104
|
+
print("EXPLORED:", ", ".join([v.name for v in explored]))
|
|
1068
1105
|
|
|
1069
1106
|
i = np.argmin([f[n] for n in frontier]) # minimum f in frontier
|
|
1070
1107
|
x = frontier.pop(i)
|
|
1071
1108
|
if verbose:
|
|
1072
|
-
print(
|
|
1109
|
+
print(" expand", x.name)
|
|
1073
1110
|
if x is G:
|
|
1074
1111
|
break
|
|
1075
1112
|
# expand the vertex
|
|
@@ -1081,7 +1118,7 @@ class PGraph(ABC):
|
|
|
1081
1118
|
f[n] = fnew
|
|
1082
1119
|
frontier.append(n)
|
|
1083
1120
|
if verbose:
|
|
1084
|
-
print(
|
|
1121
|
+
print(" add", n.name, "to the frontier")
|
|
1085
1122
|
|
|
1086
1123
|
elif n in frontier:
|
|
1087
1124
|
# neighbour is already in the frontier
|
|
@@ -1089,13 +1126,14 @@ class PGraph(ABC):
|
|
|
1089
1126
|
if fnew < f[n]:
|
|
1090
1127
|
if verbose:
|
|
1091
1128
|
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} "
|
|
1129
|
+
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} "
|
|
1130
|
+
)
|
|
1093
1131
|
f[n] = fnew
|
|
1094
1132
|
parent[n] = x
|
|
1095
1133
|
|
|
1096
1134
|
explored.append(x)
|
|
1097
1135
|
if verbose:
|
|
1098
|
-
print(
|
|
1136
|
+
print(" move", x.name, "to the explored list")
|
|
1099
1137
|
else:
|
|
1100
1138
|
# no path
|
|
1101
1139
|
return None
|
|
@@ -1117,7 +1155,8 @@ class PGraph(ABC):
|
|
|
1117
1155
|
|
|
1118
1156
|
if summary or verbose:
|
|
1119
1157
|
print(
|
|
1120
|
-
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1158
|
+
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1159
|
+
)
|
|
1121
1160
|
|
|
1122
1161
|
return path, length, parent_names
|
|
1123
1162
|
|
|
@@ -1145,11 +1184,11 @@ class PGraph(ABC):
|
|
|
1145
1184
|
if isinstance(S, str):
|
|
1146
1185
|
S = self[S]
|
|
1147
1186
|
elif not isinstance(S, Vertex):
|
|
1148
|
-
raise TypeError(
|
|
1187
|
+
raise TypeError("start must be Vertex subclass or string name")
|
|
1149
1188
|
if isinstance(G, str):
|
|
1150
1189
|
G = self[G]
|
|
1151
1190
|
elif not isinstance(S, Vertex):
|
|
1152
|
-
raise TypeError(
|
|
1191
|
+
raise TypeError("goal must be Vertex subclass or string name")
|
|
1153
1192
|
|
|
1154
1193
|
frontier = [S]
|
|
1155
1194
|
explored = []
|
|
@@ -1160,14 +1199,15 @@ class PGraph(ABC):
|
|
|
1160
1199
|
while frontier:
|
|
1161
1200
|
if verbose:
|
|
1162
1201
|
print()
|
|
1163
|
-
print(
|
|
1164
|
-
[f"{v.name}({f[v]:.0f})" for v in frontier])
|
|
1165
|
-
|
|
1202
|
+
print(
|
|
1203
|
+
"FRONTIER:", ", ".join([f"{v.name}({f[v]:.0f})" for v in frontier])
|
|
1204
|
+
)
|
|
1205
|
+
print("EXPLORED:", ", ".join([v.name for v in explored]))
|
|
1166
1206
|
|
|
1167
1207
|
i = np.argmin([f[n] for n in frontier]) # minimum f in frontier
|
|
1168
1208
|
x = frontier.pop(i)
|
|
1169
1209
|
if verbose:
|
|
1170
|
-
print(
|
|
1210
|
+
print(" expand", x.name)
|
|
1171
1211
|
if x is G:
|
|
1172
1212
|
break
|
|
1173
1213
|
# expand the vertex
|
|
@@ -1179,7 +1219,7 @@ class PGraph(ABC):
|
|
|
1179
1219
|
g[n] = g[x] + e.cost # update cost to come
|
|
1180
1220
|
f[n] = g[n] + n.heuristic_distance(G) # heuristic
|
|
1181
1221
|
if verbose:
|
|
1182
|
-
print(
|
|
1222
|
+
print(" add", n.name, "to the frontier")
|
|
1183
1223
|
elif n in frontier:
|
|
1184
1224
|
# neighbour is already in the frontier
|
|
1185
1225
|
gnew = g[x] + e.cost
|
|
@@ -1187,7 +1227,8 @@ class PGraph(ABC):
|
|
|
1187
1227
|
# cost of path via x is lower that previous, reparent it
|
|
1188
1228
|
if verbose:
|
|
1189
1229
|
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} "
|
|
1230
|
+
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} "
|
|
1231
|
+
)
|
|
1191
1232
|
g[n] = gnew
|
|
1192
1233
|
f[n] = g[n] + n.heuristic_distance(G) # heuristic
|
|
1193
1234
|
|
|
@@ -1195,7 +1236,7 @@ class PGraph(ABC):
|
|
|
1195
1236
|
|
|
1196
1237
|
explored.append(x)
|
|
1197
1238
|
if verbose:
|
|
1198
|
-
print(
|
|
1239
|
+
print(" move", x.name, "to the explored list")
|
|
1199
1240
|
|
|
1200
1241
|
else:
|
|
1201
1242
|
# no path
|
|
@@ -1218,13 +1259,15 @@ class PGraph(ABC):
|
|
|
1218
1259
|
|
|
1219
1260
|
if summary or verbose:
|
|
1220
1261
|
print(
|
|
1221
|
-
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1262
|
+
f"{len(explored)} vertices explored, {len(frontier)} remaining on the frontier"
|
|
1263
|
+
)
|
|
1222
1264
|
|
|
1223
1265
|
return path, length, parent_names
|
|
1224
1266
|
|
|
1225
1267
|
|
|
1226
1268
|
# -------------------------------------------------------------------------- #
|
|
1227
1269
|
|
|
1270
|
+
|
|
1228
1271
|
class UGraph(PGraph):
|
|
1229
1272
|
"""
|
|
1230
1273
|
Class for undirected graphs
|
|
@@ -1263,7 +1306,7 @@ class UGraph(PGraph):
|
|
|
1263
1306
|
"""
|
|
1264
1307
|
Color the graph
|
|
1265
1308
|
|
|
1266
|
-
Performs a depth-first labeling operation, assigning the ``label``
|
|
1309
|
+
Performs a depth-first labeling operation, assigning the ``label``
|
|
1267
1310
|
attribute of every vertex with a sequential integer starting from 0.
|
|
1268
1311
|
|
|
1269
1312
|
This method checks the ``_connectivitychange`` attribute of all vertices
|
|
@@ -1281,7 +1324,6 @@ class UGraph(PGraph):
|
|
|
1281
1324
|
vertex.label = None
|
|
1282
1325
|
vertex._connectivitychange = False
|
|
1283
1326
|
|
|
1284
|
-
|
|
1285
1327
|
lastlabel = None
|
|
1286
1328
|
for label in range(self.n):
|
|
1287
1329
|
assignment = False
|
|
@@ -1304,6 +1346,7 @@ class UGraph(PGraph):
|
|
|
1304
1346
|
|
|
1305
1347
|
self._ncomponents = lastlabel + 1
|
|
1306
1348
|
|
|
1349
|
+
|
|
1307
1350
|
class DGraph(PGraph):
|
|
1308
1351
|
"""
|
|
1309
1352
|
Class for directed graphs
|
|
@@ -1342,7 +1385,7 @@ class DGraph(PGraph):
|
|
|
1342
1385
|
"""
|
|
1343
1386
|
Color the graph
|
|
1344
1387
|
|
|
1345
|
-
Performs a depth-first labeling operation, assigning the ``label``
|
|
1388
|
+
Performs a depth-first labeling operation, assigning the ``label``
|
|
1346
1389
|
attribute of every vertex with a sequential integer starting from 0.
|
|
1347
1390
|
|
|
1348
1391
|
This method checks the ``_connectivitychange`` attribute of all vertices
|
|
@@ -1399,8 +1442,10 @@ class DGraph(PGraph):
|
|
|
1399
1442
|
|
|
1400
1443
|
self._ncomponents = len(unique)
|
|
1401
1444
|
|
|
1445
|
+
|
|
1402
1446
|
# ========================================================================== #
|
|
1403
1447
|
|
|
1448
|
+
|
|
1404
1449
|
class Edge:
|
|
1405
1450
|
"""
|
|
1406
1451
|
Edge class
|
|
@@ -1415,7 +1460,7 @@ class Edge:
|
|
|
1415
1460
|
|
|
1416
1461
|
.. note::
|
|
1417
1462
|
|
|
1418
|
-
- An undirected graph is created by having a single edge object in the
|
|
1463
|
+
- An undirected graph is created by having a single edge object in the
|
|
1419
1464
|
edgelist of _each_ vertex.
|
|
1420
1465
|
- This class can be inherited to provide user objects with graph capability.
|
|
1421
1466
|
- Inheritance is an alternative to providing arbitrary user data.
|
|
@@ -1468,7 +1513,6 @@ class Edge:
|
|
|
1468
1513
|
else:
|
|
1469
1514
|
self.cost = None
|
|
1470
1515
|
|
|
1471
|
-
|
|
1472
1516
|
def __repr__(self):
|
|
1473
1517
|
return str(self)
|
|
1474
1518
|
|
|
@@ -1489,16 +1533,16 @@ class Edge:
|
|
|
1489
1533
|
:type v2: Vertex subclass
|
|
1490
1534
|
|
|
1491
1535
|
The edge is added to the graph and connects vertices ``v1`` and ``v2``.
|
|
1492
|
-
|
|
1536
|
+
|
|
1493
1537
|
.. note:: The vertices must already be added to the graph.
|
|
1494
1538
|
"""
|
|
1495
1539
|
|
|
1496
1540
|
if v1._graph is None:
|
|
1497
|
-
raise ValueError(
|
|
1541
|
+
raise ValueError("vertex v1 does not belong to a graph")
|
|
1498
1542
|
if v2._graph is None:
|
|
1499
|
-
raise ValueError(
|
|
1543
|
+
raise ValueError("vertex v2 does not belong to a graph")
|
|
1500
1544
|
if not v1._graph is v2._graph:
|
|
1501
|
-
raise ValueError(
|
|
1545
|
+
raise ValueError("vertices must belong to the same graph")
|
|
1502
1546
|
|
|
1503
1547
|
# connect edge to its vertices
|
|
1504
1548
|
self.v1 = v1
|
|
@@ -1508,7 +1552,6 @@ class Edge:
|
|
|
1508
1552
|
# DGraph or UGraph
|
|
1509
1553
|
v1.connect(v2, edge=self)
|
|
1510
1554
|
|
|
1511
|
-
|
|
1512
1555
|
def next(self, vertex):
|
|
1513
1556
|
"""
|
|
1514
1557
|
Return other end of an edge
|
|
@@ -1519,7 +1562,7 @@ class Edge:
|
|
|
1519
1562
|
:return: the other vertex on the edge
|
|
1520
1563
|
:rtype: Vertex subclass
|
|
1521
1564
|
|
|
1522
|
-
``e.next(v1)`` is the vertex at the other end of edge ``e``, ie. the
|
|
1565
|
+
``e.next(v1)`` is the vertex at the other end of edge ``e``, ie. the
|
|
1523
1566
|
vertex that is not ``v1``.
|
|
1524
1567
|
"""
|
|
1525
1568
|
|
|
@@ -1528,10 +1571,10 @@ class Edge:
|
|
|
1528
1571
|
elif self.v2 is vertex:
|
|
1529
1572
|
return self.v1
|
|
1530
1573
|
else:
|
|
1531
|
-
raise ValueError(
|
|
1574
|
+
raise ValueError("shouldnt happen")
|
|
1532
1575
|
|
|
1533
1576
|
def vertices(self):
|
|
1534
|
-
raise DeprecationWarning(
|
|
1577
|
+
raise DeprecationWarning("use endpoints instead")
|
|
1535
1578
|
|
|
1536
1579
|
@property
|
|
1537
1580
|
def endpoints(self):
|
|
@@ -1558,6 +1601,7 @@ class Edge:
|
|
|
1558
1601
|
# self.v1 = None
|
|
1559
1602
|
# self.v2 = None
|
|
1560
1603
|
|
|
1604
|
+
|
|
1561
1605
|
# ========================================================================== #
|
|
1562
1606
|
|
|
1563
1607
|
|
|
@@ -1590,9 +1634,9 @@ class Vertex:
|
|
|
1590
1634
|
|
|
1591
1635
|
def __repr__(self):
|
|
1592
1636
|
if self.coord is None:
|
|
1593
|
-
coord =
|
|
1637
|
+
coord = "?"
|
|
1594
1638
|
else:
|
|
1595
|
-
coord =
|
|
1639
|
+
coord = ", ".join([f"{x:.4g}" for x in self.coord])
|
|
1596
1640
|
return f"{self.__class__.__name__}[{self.name:s}, coord=({coord})]"
|
|
1597
1641
|
|
|
1598
1642
|
def copy(self, cls=None):
|
|
@@ -1601,32 +1645,15 @@ class Vertex:
|
|
|
1601
1645
|
else:
|
|
1602
1646
|
return self.__class__(coord=self.coord, name=self.name)
|
|
1603
1647
|
|
|
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
1648
|
def neighbours(self):
|
|
1618
1649
|
"""
|
|
1619
1650
|
Neighbours of a vertex
|
|
1620
1651
|
|
|
1621
1652
|
``v.neighbours()`` is a list of neighbours of this vertex.
|
|
1622
1653
|
|
|
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`
|
|
1654
|
+
.. note:: For a directed graph the neighbours are those on edges leaving this vertex
|
|
1628
1655
|
"""
|
|
1629
|
-
return
|
|
1656
|
+
return [e.next(self) for e in self._edgelist]
|
|
1630
1657
|
|
|
1631
1658
|
def neighbors(self):
|
|
1632
1659
|
"""
|
|
@@ -1634,14 +1661,9 @@ class Vertex:
|
|
|
1634
1661
|
|
|
1635
1662
|
``v.neighbors()`` is a list of neighbors of this vertex.
|
|
1636
1663
|
|
|
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`
|
|
1664
|
+
.. note:: For a directed graph the neighbours are those on edges leaving this vertex
|
|
1642
1665
|
"""
|
|
1643
|
-
return
|
|
1644
|
-
|
|
1666
|
+
return [e.next(self) for e in self._edgelist]
|
|
1645
1667
|
|
|
1646
1668
|
def isneighbour(self, vertex):
|
|
1647
1669
|
"""
|
|
@@ -1661,7 +1683,7 @@ class Vertex:
|
|
|
1661
1683
|
"""
|
|
1662
1684
|
Neighbours and edges of a vertex
|
|
1663
1685
|
|
|
1664
|
-
``v.incidences()`` is a generator that returns a list of incidences,
|
|
1686
|
+
``v.incidences()`` is a generator that returns a list of incidences,
|
|
1665
1687
|
tuples of (vertex, edge) for all neighbours of the vertex ``v``.
|
|
1666
1688
|
|
|
1667
1689
|
.. note:: For a directed graph the edges are those leaving this vertex
|
|
@@ -1699,7 +1721,7 @@ class Vertex:
|
|
|
1699
1721
|
"""
|
|
1700
1722
|
|
|
1701
1723
|
if not dest.__class__.__bases__[0] is self.__class__.__bases__[0]:
|
|
1702
|
-
raise TypeError(
|
|
1724
|
+
raise TypeError("must connect vertices of same type")
|
|
1703
1725
|
elif isinstance(edge, Edge):
|
|
1704
1726
|
e = edge
|
|
1705
1727
|
else:
|
|
@@ -1725,10 +1747,10 @@ class Vertex:
|
|
|
1725
1747
|
|
|
1726
1748
|
- For a directed graph ``dest`` must be at the arrow end of the edge
|
|
1727
1749
|
"""
|
|
1728
|
-
for
|
|
1750
|
+
for n, e in self.incidences():
|
|
1729
1751
|
if n is dest:
|
|
1730
1752
|
return e
|
|
1731
|
-
raise ValueError(
|
|
1753
|
+
raise ValueError("dest is not a neighbour")
|
|
1732
1754
|
|
|
1733
1755
|
def edges(self):
|
|
1734
1756
|
"""
|
|
@@ -1814,6 +1836,7 @@ class Vertex:
|
|
|
1814
1836
|
def closest(self):
|
|
1815
1837
|
return self._graph.closest(self.coord)
|
|
1816
1838
|
|
|
1839
|
+
|
|
1817
1840
|
class UVertex(Vertex):
|
|
1818
1841
|
"""
|
|
1819
1842
|
Vertex subclass for undirected graphs
|
|
@@ -1832,7 +1855,7 @@ class UVertex(Vertex):
|
|
|
1832
1855
|
elif isinstance(other, Edge):
|
|
1833
1856
|
e = super().connect(edge=other)
|
|
1834
1857
|
else:
|
|
1835
|
-
raise TypeError(
|
|
1858
|
+
raise TypeError("bad argument")
|
|
1836
1859
|
|
|
1837
1860
|
# e = super().connect(other, **kwargs)
|
|
1838
1861
|
|
|
@@ -1859,7 +1882,7 @@ class DVertex(Vertex):
|
|
|
1859
1882
|
elif isinstance(other, Edge):
|
|
1860
1883
|
e = super().connect(edge=other)
|
|
1861
1884
|
else:
|
|
1862
|
-
raise TypeError(
|
|
1885
|
+
raise TypeError("bad argument")
|
|
1863
1886
|
|
|
1864
1887
|
self._edgelist.append(e)
|
|
1865
1888
|
return e
|
|
@@ -1871,10 +1894,11 @@ class DVertex(Vertex):
|
|
|
1871
1894
|
if __name__ == "__main__":
|
|
1872
1895
|
|
|
1873
1896
|
g = UGraph()
|
|
1897
|
+
print(g)
|
|
1874
1898
|
|
|
1875
1899
|
for i in range(10):
|
|
1876
1900
|
g.add_vertex()
|
|
1877
1901
|
|
|
1878
1902
|
g.add_edge(g[0], g[1])
|
|
1879
1903
|
|
|
1880
|
-
print(g)
|
|
1904
|
+
print(g)
|