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 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,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 == 'L1':
329
+ if metric == "L1":
330
330
  return L1
331
- elif metric == 'L2':
331
+ elif metric == "L2":
332
332
  return L2
333
- elif metric == 'SE2':
333
+ elif metric == "SE2":
334
334
  return SE2
335
335
  else:
336
- raise ValueError('unknown metric')
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 '\n'.join(s)
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.Inf
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(self, colorcomponents=True, force2d=False, vopt={}, eopt={}, text={}, block=False, grid=True, ax=None):
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='o', markersize=12), **vopt}
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([vertex.x, v.x], [vertex.y, v.y],
551
- color=color[c, :], **eopt)
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(vertex.x, vertex.y, vertex.z, " " + vertex.name, **text)
577
+ ax.text(
578
+ vertex.x, vertex.y, vertex.z, " " + vertex.name, **text
579
+ )
565
580
  if colorcomponents:
566
- ax.plot(vertex.x, vertex.y, vertex.z, **{**dict(color=color[c, :]), **vopt})
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([vertex.x, v.x], [vertex.y, v.y], [vertex.z, v.z],
569
- **{**dict(color=color[c, :]), **eopt})
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([vertex.x, v.x], [vertex.y, v.y], [vertex.z, v.z], **eopt)
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
- if block is not None:
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='r', alpha=0.5):
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([p1.x, p2.x], [p1.y, p2.y], color=color,
617
- linewidth=3 * scale, alpha=alpha)
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='r', alpha=0.5):
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, 'o', color=color,
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(vertex.x, vertex.y, 'o', color=color,
639
- markersize=12 * scale, alpha=alpha)
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
- `GraphViz <https://graphviz.org>`_ code to represent the embedded graph. By default output
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
- - Use ``neato`` rather than dot to get the embedded layout
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(' "{:s}" [pos="{:.5g},{:.5g}"]'.format(
685
- vertex.name, vertex.coord[0], vertex.coord[1]), file=f)
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('}', file=f)
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('start must be Vertex subclass or string name')
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('goal must be Vertex subclass or string name')
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('FRONTIER:', ", ".join([v.name for v in frontier]))
983
- print('EXPLORED:', ", ".join([v.name for v in explored]))
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(' expand', x.name)
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(' goal', n.name, 'reached')
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(' add', n.name, 'to the frontier')
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(' move', x.name, 'to the explored list')
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('start must be Vertex subclass or string name')
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('goal must be Vertex subclass or string name')
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('FRONTIER:', ", ".join(
1066
- [f"{v.name}({f[v]:.0f})" for v in frontier]))
1067
- print('EXPLORED:', ", ".join([v.name for v in explored]))
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(' expand', x.name)
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(' add', n.name, 'to the frontier')
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(' move', x.name, 'to the explored list')
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('start must be Vertex subclass or string name')
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('goal must be Vertex subclass or string name')
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('FRONTIER:', ", ".join(
1164
- [f"{v.name}({f[v]:.0f})" for v in frontier]))
1165
- print('EXPLORED:', ", ".join([v.name for v in explored]))
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(' expand', x.name)
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(' add', n.name, 'to the frontier')
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(' move', x.name, 'to the explored list')
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('vertex v1 does not belong to a graph')
1541
+ raise ValueError("vertex v1 does not belong to a graph")
1498
1542
  if v2._graph is None:
1499
- raise ValueError('vertex v2 does not belong to a graph')
1543
+ raise ValueError("vertex v2 does not belong to a graph")
1500
1544
  if not v1._graph is v2._graph:
1501
- raise ValueError('vertices must belong to the same graph')
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('shouldnt happen')
1574
+ raise ValueError("shouldnt happen")
1532
1575
 
1533
1576
  def vertices(self):
1534
- raise DeprecationWarning('use endpoints instead')
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 = ', '.join([f"{x:.4g}" for x in self.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 self.adjacent()
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 self.adjacent()
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('must connect vertices of same type')
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 (n, e) in self.incidences():
1750
+ for n, e in self.incidences():
1729
1751
  if n is dest:
1730
1752
  return e
1731
- raise ValueError('dest is not a neighbour')
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('bad argument')
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('bad argument')
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)