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 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 = 'L2'
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('Adjacency matrix must be square')
112
+ raise ValueError("Adjacency matrix must be square")
112
113
  if names is not None and len(names) != A.shape[0]:
113
- raise ValueError('length of names must match dimension of adjacency matrix')
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
- raise ValueError('coords must have same number of rows as adjacency matrix')
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('loops in graph not supported')
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('v1 must be Vertex subclass or string name')
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('v2 must be Vertex subclass or string name')
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('expecting Edge or Vertex')
261
+ raise TypeError("expecting Edge or Vertex")
262
262
 
263
263
  def show(self):
264
- print('vertices:')
264
+ print("vertices:")
265
265
  for v in self._vertexlist:
266
- print(' ' + str(v))
267
- print('edges:')
266
+ print(" " + str(v))
267
+ print("edges:")
268
268
  for e in self._edgelist:
269
- print(' ' + str(e))
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 == 'L1':
332
+ if metric == "L1":
330
333
  return L1
331
- elif metric == 'L2':
334
+ elif metric == "L2":
332
335
  return L2
333
- elif metric == 'SE2':
336
+ elif metric == "SE2":
334
337
  return SE2
335
338
  else:
336
- raise ValueError('unknown metric')
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 '\n'.join(s)
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.Inf
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(self, colorcomponents=True, force2d=False, vopt={}, eopt={}, text={}, block=False, grid=True, ax=None):
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='o', markersize=12), **vopt}
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([vertex.x, v.x], [vertex.y, v.y],
551
- color=color[c, :], **eopt)
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(vertex.x, vertex.y, vertex.z, " " + vertex.name, **text)
580
+ ax.text(
581
+ vertex.x, vertex.y, vertex.z, " " + vertex.name, **text
582
+ )
565
583
  if colorcomponents:
566
- ax.plot(vertex.x, vertex.y, vertex.z, **{**dict(color=color[c, :]), **vopt})
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([vertex.x, v.x], [vertex.y, v.y], [vertex.z, v.z],
569
- **{**dict(color=color[c, :]), **eopt})
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([vertex.x, v.x], [vertex.y, v.y], [vertex.z, v.z], **eopt)
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
- plt.show(block=block)
632
+ if block is not None:
633
+ plt.show(block=block)
601
634
 
602
- def highlight_edge(self, edge, scale=2, color='r', alpha=0.5):
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([p1.x, p2.x], [p1.y, p2.y], color=color,
617
- linewidth=3 * scale, alpha=alpha)
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='r', alpha=0.5):
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, 'o', color=color,
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(vertex.x, vertex.y, 'o', color=color,
639
- markersize=12 * scale, alpha=alpha)
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
- `GraphViz <https://graphviz.org>`_ code to represent the embedded graph. By default output
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
- - Use ``neato`` rather than dot to get the embedded layout
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(' "{:s}" [pos="{:.5g},{:.5g}"]'.format(
685
- vertex.name, vertex.coord[0], vertex.coord[1]), file=f)
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('}', file=f)
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('start must be Vertex subclass or string name')
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('goal must be Vertex subclass or string name')
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('FRONTIER:', ", ".join([v.name for v in frontier]))
983
- print('EXPLORED:', ", ".join([v.name for v in explored]))
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(' expand', x.name)
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(' goal', n.name, 'reached')
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(' add', n.name, 'to the frontier')
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(' move', x.name, 'to the explored list')
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('start must be Vertex subclass or string name')
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('goal must be Vertex subclass or string name')
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('FRONTIER:', ", ".join(
1066
- [f"{v.name}({f[v]:.0f})" for v in frontier]))
1067
- print('EXPLORED:', ", ".join([v.name for v in explored]))
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(' expand', x.name)
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(' add', n.name, 'to the frontier')
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(' move', x.name, 'to the explored list')
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('start must be Vertex subclass or string name')
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('goal must be Vertex subclass or string name')
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('FRONTIER:', ", ".join(
1164
- [f"{v.name}({f[v]:.0f})" for v in frontier]))
1165
- print('EXPLORED:', ", ".join([v.name for v in explored]))
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(' expand', x.name)
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(' add', n.name, 'to the frontier')
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(' move', x.name, 'to the explored list')
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
- self._ncomponents = len(unique)
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('vertex v1 does not belong to a graph')
1549
+ raise ValueError("vertex v1 does not belong to a graph")
1498
1550
  if v2._graph is None:
1499
- raise ValueError('vertex v2 does not belong to a graph')
1551
+ raise ValueError("vertex v2 does not belong to a graph")
1500
1552
  if not v1._graph is v2._graph:
1501
- raise ValueError('vertices must belong to the same graph')
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('shouldnt happen')
1582
+ raise ValueError("shouldnt happen")
1532
1583
 
1533
1584
  def vertices(self):
1534
- raise DeprecationWarning('use endpoints instead')
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 = ', '.join([f"{x:.4g}" for x in self.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 self.adjacent()
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 self.adjacent()
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('must connect vertices of same type')
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 (n, e) in self.incidences():
1758
+ for n, e in self.incidences():
1729
1759
  if n is dest:
1730
1760
  return e
1731
- raise ValueError('dest is not a neighbour')
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('bad argument')
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('bad argument')
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)