diff --git a/pymathics/graph/base.py b/pymathics/graph/base.py
index 211532d..f154221 100644
--- a/pymathics/graph/base.py
+++ b/pymathics/graph/base.py
@@ -4,13 +4,13 @@
Core routines for working with Graphs.
"""
-# uses networkx
+# uses NetworkX
from collections import defaultdict
from inspect import isgenerator
from typing import Callable, Optional, Union
-from mathics.builtin.no_meaning import (
+from mathics.builtin.no_meaning.infix_extra import (
DirectedEdge as GenericDirectedEdge,
UndirectedEdge as GenericUndirectedEdge,
)
@@ -19,6 +19,7 @@
from mathics.core.atoms import Atom, Integer, Integer0, Integer1, Integer2, String
from mathics.core.convert.expression import ListExpression, from_python, to_mathics_list
from mathics.core.element import BaseElement
+from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.pattern import pattern_objects
from mathics.core.symbols import Symbol, SymbolList, SymbolTrue
@@ -133,133 +134,6 @@ def has_directed_option(options: dict) -> bool:
return options.get("System`DirectedEdges", False)
-def _process_graph_options(g, options: dict) -> None:
- """
- Handle common graph-related options like VertexLabels, PlotLabel, VertexShape, etc.
- """
- # FIXME: for now we are adding both to both g and g.G.
- # g is where it is used in format. However we should wrap this as our object.
- # Access in G which might be better, currently isn't used.
- g.G.vertex_labels = g.vertex_labels = (
- options["System`VertexLabels"].to_python()
- if "System`VertexLabels" in options
- else False
- )
- shape = (
- options["System`VertexShape"].get_string_value()
- if "System`VertexShape" in options
- else "Circle"
- )
-
- g.G.node_shape = g.node_shape = WL_MARKER_TO_NETWORKX.get(shape, shape)
-
- color = (
- options["System`VertexStyle"].get_string_value()
- if "System`VertexStyle" in options
- else "Blue"
- )
-
- g.graph_layout = (
- options["System`GraphLayout"].get_string_value()
- if "System`GraphLayout" in options
- else ""
- )
-
- g.G.graph_layout = g.graph_layout = WL_LAYOUT_TO_NETWORKX.get(
- g.graph_layout, g.graph_layout
- )
-
- g.G.node_color = g.node_color = WL_COLOR_TO_NETWORKX.get(color, color)
-
- g.G.title = g.title = (
- options["System`PlotLabel"].get_string_value()
- if "System`PlotLabel" in options
- else None
- )
-
-
-def _circular_layout(G):
- return nx.drawing.circular_layout(G, scale=1)
-
-
-def _spectral_layout(G):
- return nx.drawing.spectral_layout(G, scale=2)
-
-
-def _shell_layout(G):
- return nx.drawing.shell_layout(G, scale=2)
-
-
-def _generic_layout(G, warn):
- return nx.nx_pydot.graphviz_layout(G, prog="dot")
-
-
-def _path_layout(G, root):
- v = root
- x = 0
- y = 0
-
- k = 0
- d = 0
-
- pos = {}
- neighbors = G.neighbors(v)
-
- for _ in range(len(G)):
- pos[v] = (x, y)
-
- if not neighbors:
- break
- try:
- v = next(neighbors) if isgenerator(neighbors) else neighbors[0]
- except StopIteration:
- break
- neighbors = G.neighbors(v)
-
- if k == 0:
- if d < 1 or neighbors:
- d += 1
- x += d
- elif k == 1:
- y += d
- elif k == 2:
- if neighbors:
- d += 1
- x -= d
- elif k == 3:
- y -= d
-
- k = (k + 1) % 4
-
- return pos
-
-
-def _auto_layout(G, warn):
- path_root = None
-
- for v, d in G.degree(G.nodes):
- if d == 1 and G.neighbors(v):
- path_root = v
- elif d > 2:
- path_root = None
- break
-
- if path_root is not None:
- return _path_layout(G, path_root)
- else:
- return _generic_layout(G, warn)
-
-
-def _convert_networkx_graph(G, options):
- mapping = dict((v, Integer(i)) for i, v in enumerate(G.nodes))
- G = nx.relabel_nodes(G, mapping)
- [Expression(SymbolUndirectedEdge, u, v) for u, v in G.edges]
- return Graph(
- G,
- **options,
- )
-
-
_default_minimum_distance = 0.3
@@ -424,7 +298,7 @@ def __hash__(self):
def __str__(self):
return "-Graph-"
- def atom_to_boxes(self, f, evaluation) -> "GraphBox":
+ def atom_to_boxes(self, f, evaluation: Evaluation) -> "GraphBox":
return GraphBox(self.G)
def add_edges(self, new_edges, new_edge_properties):
@@ -439,7 +313,7 @@ def add_vertices(self, *vertices_to_add):
G.add_nodes_from(vertices_to_add)
return Graph(G)
- def coalesced_graph(self, evaluation):
+ def coalesced_graph(self, evaluation: Evaluation):
if not isinstance(self.G, (nx.MultiDiGraph, nx.MultiGraph)):
return self.G, "WEIGHT"
@@ -603,6 +477,133 @@ def _graph_from_list(rules, options, new_vertices=None):
)
+def _process_graph_options(g: Graph, options: dict) -> None:
+ """
+ Handle common graph-related options like VertexLabels, PlotLabel, VertexShape, etc.
+ """
+ # FIXME: for now we are adding both to both g and g.G.
+ # g is where it is used in format. However we should wrap this as our object.
+ # Access in G which might be better, currently isn't used.
+ g.G.vertex_labels = g.vertex_labels = (
+ options["System`VertexLabels"].to_python()
+ if "System`VertexLabels" in options
+ else False
+ )
+ shape = (
+ options["System`VertexShape"].get_string_value()
+ if "System`VertexShape" in options
+ else "Circle"
+ )
+
+ g.G.node_shape = g.node_shape = WL_MARKER_TO_NETWORKX.get(shape, shape)
+
+ color = (
+ options["System`VertexStyle"].get_string_value()
+ if "System`VertexStyle" in options
+ else "Blue"
+ )
+
+ g.graph_layout = (
+ options["System`GraphLayout"].get_string_value()
+ if "System`GraphLayout" in options
+ else ""
+ )
+
+ g.G.graph_layout = g.graph_layout = WL_LAYOUT_TO_NETWORKX.get(
+ g.graph_layout, g.graph_layout
+ )
+
+ g.G.node_color = g.node_color = WL_COLOR_TO_NETWORKX.get(color, color)
+
+ g.G.title = g.title = (
+ options["System`PlotLabel"].get_string_value()
+ if "System`PlotLabel" in options
+ else None
+ )
+
+
+def _circular_layout(G: Graph):
+ return nx.drawing.circular_layout(G, scale=1)
+
+
+def _spectral_layout(G: Graph):
+ return nx.drawing.spectral_layout(G, scale=2)
+
+
+def _shell_layout(G: Graph):
+ return nx.drawing.shell_layout(G, scale=2)
+
+
+def _generic_layout(G, warn):
+ return nx.nx_pydot.graphviz_layout(G, prog="dot")
+
+
+def _path_layout(G, root):
+ v = root
+ x = 0
+ y = 0
+
+ k = 0
+ d = 0
+
+ pos = {}
+ neighbors = G.neighbors(v)
+
+ for _ in range(len(G)):
+ pos[v] = (x, y)
+
+ if not neighbors:
+ break
+ try:
+ v = next(neighbors) if isgenerator(neighbors) else neighbors[0]
+ except StopIteration:
+ break
+ neighbors = G.neighbors(v)
+
+ if k == 0:
+ if d < 1 or neighbors:
+ d += 1
+ x += d
+ elif k == 1:
+ y += d
+ elif k == 2:
+ if neighbors:
+ d += 1
+ x -= d
+ elif k == 3:
+ y -= d
+
+ k = (k + 1) % 4
+
+ return pos
+
+
+def _auto_layout(G: Graph, warn):
+ path_root = None
+
+ for v, d in G.degree(G.nodes):
+ if d == 1 and G.neighbors(v):
+ path_root = v
+ elif d > 2:
+ path_root = None
+ break
+
+ if path_root is not None:
+ return _path_layout(G, path_root)
+ else:
+ return _generic_layout(G, warn)
+
+
+def _convert_networkx_graph(G: Graph, options: dict):
+ mapping = dict((v, Integer(i)) for i, v in enumerate(G.nodes))
+ G = nx.relabel_nodes(G, mapping)
+ [Expression(SymbolUndirectedEdge, u, v) for u, v in G.edges]
+ return Graph(
+ G,
+ **options,
+ )
+
+
def _create_graph(
new_edges, new_edge_properties, options, from_graph=None, new_vertices=None
):
@@ -813,7 +814,9 @@ def full_new_edge_properties(new_edge_style):
class _PatternList(_NetworkXBuiltin):
- def eval(self, graph, expression, evaluation, options):
+ def eval(
+ self, graph, expression: Expression, evaluation: Evaluation, options: dict
+ ):
"%(name)s[graph_, OptionsPattern[%(name)s]]"
graph = self._build_graph(graph, evaluation, options, expression)
if graph:
@@ -903,8 +906,6 @@ class DirectedEdge(GenericDirectedEdge):
Edge of a
:Directed graph:
https://en.wikipedia.org/wiki/Directed_graph (
- :NetworkX:
- https://networkx.org/documentation/networkx-2.8.8/reference/classes/digraph.html,
:WMA:
https://reference.wolfram.com/language/ref/DirectedEdge.html)
@@ -921,6 +922,10 @@ class DirectedEdge(GenericDirectedEdge):
= a → b
"""
+ # attributes change from the default, so we need to
+ # specify this below. Other things like "formats",
+ # "operator" and "summary" do not change from the default.
+
attributes = A_PROTECTED | A_READ_PROTECTED
@@ -1088,7 +1093,9 @@ class FindShortestPath(_NetworkXBuiltin):
summary_text = "find the shortest path between two vertices"
- def eval_s_t(self, graph, s, t, expression, evaluation, options):
+ def eval_s_t(
+ self, graph, s, t, expression: Expression, evaluation: Evaluation, options: dict
+ ):
"FindShortestPath[graph_, s_, t_, OptionsPattern[FindShortestPath]]"
graph = self._build_graph(graph, evaluation, options, expression)
if not graph:
@@ -1507,7 +1514,9 @@ def _items(self, graph):
class UndirectedEdge(GenericUndirectedEdge):
"""
-
+ Edge of a
+ :Undirected graph:
+ https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)#Undirected_graph
:WMA link:
https://reference.wolfram.com/language/ref/UndirectedEdge.html
@@ -1520,6 +1529,10 @@ class UndirectedEdge(GenericUndirectedEdge):
= ...
"""
+ # attributes change from the default, so we need to
+ # specify this below. Other things like "formats",
+ # "operator" and "summary" do not change from the default.
+
attributes = A_PROTECTED | A_READ_PROTECTED