Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

track module change in mathics-core... #36

Merged
merged 1 commit into from
Nov 27, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 149 additions & 136 deletions pymathics/graph/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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):
Expand All @@ -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"

Expand Down Expand Up @@ -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
):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -903,8 +906,6 @@ class DirectedEdge(GenericDirectedEdge):
Edge of a <url>
:Directed graph:
https://en.wikipedia.org/wiki/Directed_graph</url> (<url>
:NetworkX:
https://networkx.org/documentation/networkx-2.8.8/reference/classes/digraph.html</url>, <url>
:WMA:
https://reference.wolfram.com/language/ref/DirectedEdge.html</url>)

Expand All @@ -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


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -1507,7 +1514,9 @@ def _items(self, graph):

class UndirectedEdge(GenericUndirectedEdge):
"""
<url>
Edge of a <url>
:Undirected graph:
https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)#Undirected_graph</url> <url>
:WMA link:
https://reference.wolfram.com/language/ref/UndirectedEdge.html</url>

Expand All @@ -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


Expand Down