44Core routines for working with Graphs.
55"""
66
7- # uses networkx
7+ # uses NetworkX
88
99from collections import defaultdict
1010from inspect import isgenerator
1111from typing import Callable , Optional , Union
1212
13- from mathics .builtin .no_meaning import (
13+ from mathics .builtin .no_meaning . infix_extra import (
1414 DirectedEdge as GenericDirectedEdge ,
1515 UndirectedEdge as GenericUndirectedEdge ,
1616)
1919from mathics .core .atoms import Atom , Integer , Integer0 , Integer1 , Integer2 , String
2020from mathics .core .convert .expression import ListExpression , from_python , to_mathics_list
2121from mathics .core .element import BaseElement
22+ from mathics .core .evaluation import Evaluation
2223from mathics .core .expression import Expression
2324from mathics .core .pattern import pattern_objects
2425from mathics .core .symbols import Symbol , SymbolList , SymbolTrue
@@ -133,133 +134,6 @@ def has_directed_option(options: dict) -> bool:
133134 return options .get ("System`DirectedEdges" , False )
134135
135136
136- def _process_graph_options (g , options : dict ) -> None :
137- """
138- Handle common graph-related options like VertexLabels, PlotLabel, VertexShape, etc.
139- """
140- # FIXME: for now we are adding both to both g and g.G.
141- # g is where it is used in format. However we should wrap this as our object.
142- # Access in G which might be better, currently isn't used.
143- g .G .vertex_labels = g .vertex_labels = (
144- options ["System`VertexLabels" ].to_python ()
145- if "System`VertexLabels" in options
146- else False
147- )
148- shape = (
149- options ["System`VertexShape" ].get_string_value ()
150- if "System`VertexShape" in options
151- else "Circle"
152- )
153-
154- g .G .node_shape = g .node_shape = WL_MARKER_TO_NETWORKX .get (shape , shape )
155-
156- color = (
157- options ["System`VertexStyle" ].get_string_value ()
158- if "System`VertexStyle" in options
159- else "Blue"
160- )
161-
162- g .graph_layout = (
163- options ["System`GraphLayout" ].get_string_value ()
164- if "System`GraphLayout" in options
165- else ""
166- )
167-
168- g .G .graph_layout = g .graph_layout = WL_LAYOUT_TO_NETWORKX .get (
169- g .graph_layout , g .graph_layout
170- )
171-
172- g .G .node_color = g .node_color = WL_COLOR_TO_NETWORKX .get (color , color )
173-
174- g .G .title = g .title = (
175- options ["System`PlotLabel" ].get_string_value ()
176- if "System`PlotLabel" in options
177- else None
178- )
179-
180-
181- def _circular_layout (G ):
182- return nx .drawing .circular_layout (G , scale = 1 )
183-
184-
185- def _spectral_layout (G ):
186- return nx .drawing .spectral_layout (G , scale = 2 )
187-
188-
189- def _shell_layout (G ):
190- return nx .drawing .shell_layout (G , scale = 2 )
191-
192-
193- def _generic_layout (G , warn ):
194- return nx .nx_pydot .graphviz_layout (G , prog = "dot" )
195-
196-
197- def _path_layout (G , root ):
198- v = root
199- x = 0
200- y = 0
201-
202- k = 0
203- d = 0
204-
205- pos = {}
206- neighbors = G .neighbors (v )
207-
208- for _ in range (len (G )):
209- pos [v ] = (x , y )
210-
211- if not neighbors :
212- break
213- try :
214- v = next (neighbors ) if isgenerator (neighbors ) else neighbors [0 ]
215- except StopIteration :
216- break
217- neighbors = G .neighbors (v )
218-
219- if k == 0 :
220- if d < 1 or neighbors :
221- d += 1
222- x += d
223- elif k == 1 :
224- y += d
225- elif k == 2 :
226- if neighbors :
227- d += 1
228- x -= d
229- elif k == 3 :
230- y -= d
231-
232- k = (k + 1 ) % 4
233-
234- return pos
235-
236-
237- def _auto_layout (G , warn ):
238- path_root = None
239-
240- for v , d in G .degree (G .nodes ):
241- if d == 1 and G .neighbors (v ):
242- path_root = v
243- elif d > 2 :
244- path_root = None
245- break
246-
247- if path_root is not None :
248- return _path_layout (G , path_root )
249- else :
250- return _generic_layout (G , warn )
251-
252-
253- def _convert_networkx_graph (G , options ):
254- mapping = dict ((v , Integer (i )) for i , v in enumerate (G .nodes ))
255- G = nx .relabel_nodes (G , mapping )
256- [Expression (SymbolUndirectedEdge , u , v ) for u , v in G .edges ]
257- return Graph (
258- G ,
259- ** options ,
260- )
261-
262-
263137_default_minimum_distance = 0.3
264138
265139
@@ -424,7 +298,7 @@ def __hash__(self):
424298 def __str__ (self ):
425299 return "-Graph-"
426300
427- def atom_to_boxes (self , f , evaluation ) -> "GraphBox" :
301+ def atom_to_boxes (self , f , evaluation : Evaluation ) -> "GraphBox" :
428302 return GraphBox (self .G )
429303
430304 def add_edges (self , new_edges , new_edge_properties ):
@@ -439,7 +313,7 @@ def add_vertices(self, *vertices_to_add):
439313 G .add_nodes_from (vertices_to_add )
440314 return Graph (G )
441315
442- def coalesced_graph (self , evaluation ):
316+ def coalesced_graph (self , evaluation : Evaluation ):
443317 if not isinstance (self .G , (nx .MultiDiGraph , nx .MultiGraph )):
444318 return self .G , "WEIGHT"
445319
@@ -603,6 +477,133 @@ def _graph_from_list(rules, options, new_vertices=None):
603477 )
604478
605479
480+ def _process_graph_options (g : Graph , options : dict ) -> None :
481+ """
482+ Handle common graph-related options like VertexLabels, PlotLabel, VertexShape, etc.
483+ """
484+ # FIXME: for now we are adding both to both g and g.G.
485+ # g is where it is used in format. However we should wrap this as our object.
486+ # Access in G which might be better, currently isn't used.
487+ g .G .vertex_labels = g .vertex_labels = (
488+ options ["System`VertexLabels" ].to_python ()
489+ if "System`VertexLabels" in options
490+ else False
491+ )
492+ shape = (
493+ options ["System`VertexShape" ].get_string_value ()
494+ if "System`VertexShape" in options
495+ else "Circle"
496+ )
497+
498+ g .G .node_shape = g .node_shape = WL_MARKER_TO_NETWORKX .get (shape , shape )
499+
500+ color = (
501+ options ["System`VertexStyle" ].get_string_value ()
502+ if "System`VertexStyle" in options
503+ else "Blue"
504+ )
505+
506+ g .graph_layout = (
507+ options ["System`GraphLayout" ].get_string_value ()
508+ if "System`GraphLayout" in options
509+ else ""
510+ )
511+
512+ g .G .graph_layout = g .graph_layout = WL_LAYOUT_TO_NETWORKX .get (
513+ g .graph_layout , g .graph_layout
514+ )
515+
516+ g .G .node_color = g .node_color = WL_COLOR_TO_NETWORKX .get (color , color )
517+
518+ g .G .title = g .title = (
519+ options ["System`PlotLabel" ].get_string_value ()
520+ if "System`PlotLabel" in options
521+ else None
522+ )
523+
524+
525+ def _circular_layout (G : Graph ):
526+ return nx .drawing .circular_layout (G , scale = 1 )
527+
528+
529+ def _spectral_layout (G : Graph ):
530+ return nx .drawing .spectral_layout (G , scale = 2 )
531+
532+
533+ def _shell_layout (G : Graph ):
534+ return nx .drawing .shell_layout (G , scale = 2 )
535+
536+
537+ def _generic_layout (G , warn ):
538+ return nx .nx_pydot .graphviz_layout (G , prog = "dot" )
539+
540+
541+ def _path_layout (G , root ):
542+ v = root
543+ x = 0
544+ y = 0
545+
546+ k = 0
547+ d = 0
548+
549+ pos = {}
550+ neighbors = G .neighbors (v )
551+
552+ for _ in range (len (G )):
553+ pos [v ] = (x , y )
554+
555+ if not neighbors :
556+ break
557+ try :
558+ v = next (neighbors ) if isgenerator (neighbors ) else neighbors [0 ]
559+ except StopIteration :
560+ break
561+ neighbors = G .neighbors (v )
562+
563+ if k == 0 :
564+ if d < 1 or neighbors :
565+ d += 1
566+ x += d
567+ elif k == 1 :
568+ y += d
569+ elif k == 2 :
570+ if neighbors :
571+ d += 1
572+ x -= d
573+ elif k == 3 :
574+ y -= d
575+
576+ k = (k + 1 ) % 4
577+
578+ return pos
579+
580+
581+ def _auto_layout (G : Graph , warn ):
582+ path_root = None
583+
584+ for v , d in G .degree (G .nodes ):
585+ if d == 1 and G .neighbors (v ):
586+ path_root = v
587+ elif d > 2 :
588+ path_root = None
589+ break
590+
591+ if path_root is not None :
592+ return _path_layout (G , path_root )
593+ else :
594+ return _generic_layout (G , warn )
595+
596+
597+ def _convert_networkx_graph (G : Graph , options : dict ):
598+ mapping = dict ((v , Integer (i )) for i , v in enumerate (G .nodes ))
599+ G = nx .relabel_nodes (G , mapping )
600+ [Expression (SymbolUndirectedEdge , u , v ) for u , v in G .edges ]
601+ return Graph (
602+ G ,
603+ ** options ,
604+ )
605+
606+
606607def _create_graph (
607608 new_edges , new_edge_properties , options , from_graph = None , new_vertices = None
608609):
@@ -813,7 +814,9 @@ def full_new_edge_properties(new_edge_style):
813814
814815
815816class _PatternList (_NetworkXBuiltin ):
816- def eval (self , graph , expression , evaluation , options ):
817+ def eval (
818+ self , graph , expression : Expression , evaluation : Evaluation , options : dict
819+ ):
817820 "%(name)s[graph_, OptionsPattern[%(name)s]]"
818821 graph = self ._build_graph (graph , evaluation , options , expression )
819822 if graph :
@@ -903,8 +906,6 @@ class DirectedEdge(GenericDirectedEdge):
903906 Edge of a <url>
904907 :Directed graph:
905908 https://en.wikipedia.org/wiki/Directed_graph</url> (<url>
906- :NetworkX:
907- https://networkx.org/documentation/networkx-2.8.8/reference/classes/digraph.html</url>, <url>
908909 :WMA:
909910 https://reference.wolfram.com/language/ref/DirectedEdge.html</url>)
910911
@@ -921,6 +922,10 @@ class DirectedEdge(GenericDirectedEdge):
921922 = a → b
922923 """
923924
925+ # attributes change from the default, so we need to
926+ # specify this below. Other things like "formats",
927+ # "operator" and "summary" do not change from the default.
928+
924929 attributes = A_PROTECTED | A_READ_PROTECTED
925930
926931
@@ -1088,7 +1093,9 @@ class FindShortestPath(_NetworkXBuiltin):
10881093
10891094 summary_text = "find the shortest path between two vertices"
10901095
1091- def eval_s_t (self , graph , s , t , expression , evaluation , options ):
1096+ def eval_s_t (
1097+ self , graph , s , t , expression : Expression , evaluation : Evaluation , options : dict
1098+ ):
10921099 "FindShortestPath[graph_, s_, t_, OptionsPattern[FindShortestPath]]"
10931100 graph = self ._build_graph (graph , evaluation , options , expression )
10941101 if not graph :
@@ -1507,7 +1514,9 @@ def _items(self, graph):
15071514
15081515class UndirectedEdge (GenericUndirectedEdge ):
15091516 """
1510- <url>
1517+ Edge of a <url>
1518+ :Undirected graph:
1519+ https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)#Undirected_graph</url> <url>
15111520 :WMA link:
15121521 https://reference.wolfram.com/language/ref/UndirectedEdge.html</url>
15131522
@@ -1520,6 +1529,10 @@ class UndirectedEdge(GenericUndirectedEdge):
15201529 = ...
15211530 """
15221531
1532+ # attributes change from the default, so we need to
1533+ # specify this below. Other things like "formats",
1534+ # "operator" and "summary" do not change from the default.
1535+
15231536 attributes = A_PROTECTED | A_READ_PROTECTED
15241537
15251538
0 commit comments