Skip to content

Commit

Permalink
pymathics`Graph now working with Mathics 5.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
rocky committed Jul 21, 2022
1 parent f568a01 commit e808963
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 98 deletions.
172 changes: 85 additions & 87 deletions pymathics/graph/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from mathics.builtin.base import Builtin, AtomBuiltin
from mathics.builtin.box.graphics import GraphicsBox
from mathics.builtin.box.inout import _BoxedString
from mathics.builtin.patterns import Matcher
from mathics.core.atoms import Integer, Integer0, Integer1, Real
from mathics.core.convert.expression import ListExpression
Expand All @@ -27,7 +28,6 @@
SymbolRGBColor,
SymbolRule,
)

from inspect import isgenerator

WL_MARKER_TO_NETWORKX = {
Expand Down Expand Up @@ -416,24 +416,39 @@ def _normalize_edges(edges):


class Graph(Atom):
class_head_name = "Pymathics`Graph"

options = DEFAULT_GRAPH_OPTIONS

def __init__(self, G, **kwargs):
super(Graph, self).__init__()
self.G = G

def __hash__(self):
return hash(("Pymathics`Graph", self.G))

def __str__(self):
return "-Graph-"

def atom_to_boxes(self, f, evaluation) -> _BoxedString:
return _BoxedString("-Graph-")

def default_format(self, evaluation, form):
return "-Graph-"

def do_format(self, evaluation, form):
return self

@property
def edges(self):
return self.G.edges

@property
def vertices(self):
return self.G.nodes

def empty(self):
return len(self.G) == 0

def is_loop_free(self):
return not any(True for _ in nx.nodes_with_selfloops(self.G))

# networkx graphs can't be for mixed
def is_mixed_graph(self):
return False
Expand All @@ -442,100 +457,85 @@ def is_mixed_graph(self):
def is_multigraph(self):
return isinstance(self.G, (nx.MultiDiGraph, nx.MultiGraph))

def is_loop_free(self):
return not any(True for _ in nx.nodes_with_selfloops(self.G))

def __str__(self):
return "-Graph-"

def do_copy(self):
return Graph(self.G)

def get_sort_key(self, pattern_sort=False):
if pattern_sort:
return super(Graph, self).get_sort_key(True)
else:
return hash(self)

def default_format(self, evaluation, form):
return "-Graph-"
@property
def vertices(self):
return self.G.nodes

def same(self, other):
return isinstance(other, Graph) and self.G == other.G
# FIXME
# self.properties == other.properties
# self.options == other.options
# self.highlights == other.highlights

def to_python(self, *args, **kwargs):
return self.G
class _Collection(object):
def __init__(self, expressions, properties=None, index=None):
self.expressions = expressions
self.properties = properties if properties else None
self.index = index

def __hash__(self):
return hash(("Graph", self.G)) # FIXME self.properties, ...
def clone(self):
properties = self.properties
return _Collection(
self.expressions[:], properties[:] if properties else None, None
)

def atom_to_boxes(self, form, evaluation):
return Expression(SymbolGraphBox, self, form)
def filter(self, expressions):
index = self.get_index()
return [expr for expr in expressions if expr in index]

def boxes_to_xml(self, **options):
# Figure out what to do here.
return "-Graph-XML-"
def extend(self, expressions, properties):
if properties:
if self.properties is None:
self.properties = [None] * len(self.expressions)
self.properties.extend(properties)
self.expressions.extend(expressions)
self.index = None
return expressions

def get_property(self, element, name):
if element.get_head_name() in ("System`DirectedEdge", "System`UndirectedEdge"):
x = self.edges.get_property(element, name)
if x is None:
x = self.vertices.get_property(element, name)
return x
def delete(self, expressions):
index = self.get_index()
trash = set(index[x] for x in expressions)
deleted = [self.expressions[i] for i in trash]
self.expressions = [x for i, x in enumerate(self.expressions) if i not in trash]
self.properties = [x for i, x in enumerate(self.properties) if i not in trash]
self.index = None
return deleted

def delete_edges(self, edges_to_delete):
G = self.G.copy()
directed = G.is_directed()

edges_to_delete = list(_normalize_edges(edges_to_delete))
# FIXME: edges_to_delete is needs to be a tuple. tuples
# are edges in networkx
edges_to_delete = [edge for edge in self.edges if edge in edges_to_delete]

for edge in edges_to_delete:
if edge.has_form("DirectedEdge", 2):
if directed:
u, v = edge.elements
G.remove_edge(u, v)
elif edge.has_form("UndirectedEdge", 2):
u, v = edge.elements
if directed:
G.remove_edge(u, v)
G.remove_edge(v, u)
else:
G.remove_edge(u, v)

edges = self.edges.clone()
edges.delete(edges_to_delete)

return Graph(G)

def update_weights(self, evaluation):
weights = None
G = self.G

if self.is_multigraph():
for u, v, k, w in G.edges.data(
"System`EdgeWeight", default=None, keys=True
):
data = G.get_edge_data(u, v, key=k)
w = data.get()
if w is not None:
w = w.evaluate(evaluation).to_mpmath()
G[u][v][k]["WEIGHT"] = w
weights = "WEIGHT"
def data(self):
return self.expressions, list(self.get_properties())

def get_index(self):
index = self.index
if index is None:
index = dict((v, i) for i, v in enumerate(self.expressions))
self.index = index
return index

def get_properties(self):
if self.properties:
for p in self.properties:
yield p
else:
for u, v, w in G.edges.data("System`EdgeWeight", default=None):
if w is not None:
w = w.evaluate(evaluation).to_mpmath()
G[u][v]["WEIGHT"] = w
weights = "WEIGHT"
for _ in range(len(self.expressions)):
yield None

return weights
def get_sorted(self):
index = self.get_index()
return lambda c: sorted(c, key=lambda v: index[v])

def get_property(self, element, name):
properties = self.properties
if properties is None:
return None
index = self.get_index()
i = index.get(element)
if i is None:
return None
p = properties[i]
if p is None:
return None
return p.get(name)


def _is_connected(G):
Expand Down Expand Up @@ -1754,9 +1754,7 @@ def apply(self, graph, evaluation, options):

def degrees(graph):
degrees = dict(list(graph.G.degree(graph.vertices)))
return ListExpression(
*[Integer(degrees.get(v, 0)) for v in graph.vertices]
)
return ListExpression(*[Integer(degrees.get(v, 0)) for v in graph.vertices])

return self._evaluate_atom(graph, options, degrees)

Expand Down
19 changes: 10 additions & 9 deletions pymathics/graph/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@

from pymathics.graph.__main__ import (
Graph,
SymbolUndirectedEdge,
_NetworkXBuiltin,
_convert_networkx_graph,
_graph_from_list,
has_directed_option,
_process_graph_options,
has_directed_option,
nx,
)

Expand Down Expand Up @@ -43,7 +44,7 @@ def graph_helper(
else graph_generator_func(*args, **kwargs)
)
except MemoryError:
evaluation.message(self.get_name(), "mem", expression)
evaluation.message("Graph", "mem", evaluation)
return None
if graph_layout and not options["System`GraphLayout"].get_string_value():
options["System`GraphLayout"] = String(graph_layout)
Expand Down Expand Up @@ -287,7 +288,7 @@ def apply(self, k, n, expression, evaluation, options):
n_int = n.get_int_value()
k_int = k.get_int_value()

new_n_int = int(((k_int ** n_int) - 1) / (k_int - 1))
new_n_int = int(((k_int**n_int) - 1) / (k_int - 1))
return f_r_t_apply(self, k, Integer(new_n_int), expression, evaluation, options)

# FIXME: can be done with rules?
Expand All @@ -296,7 +297,7 @@ def apply_2(self, n, expression, evaluation, options):

n_int = n.get_int_value()

new_n_int = int(2 ** n_int) - 1
new_n_int = int(2**n_int) - 1
return f_r_t_apply(
self, Integer(2), Integer(new_n_int), expression, evaluation, options
)
Expand Down Expand Up @@ -585,13 +586,13 @@ class PathGraph(_NetworkXBuiltin):
= -Graph-
"""

def apply(self, l, evaluation, options):
def apply(self, element, evaluation, options):
"PathGraph[l_List, OptionsPattern[%(name)s]]"
leaves = l.leaves
elements = element.elements

def edges():
for u, v in zip(leaves, leaves[1:]):
yield Expression("UndirectedEdge", u, v)
for u, v in zip(elements, elements[1:]):
yield Expression(SymbolUndirectedEdge, u, v)

g = _graph_from_list(edges(), options)
g.G.graph_layout = (
Expand All @@ -617,7 +618,7 @@ def _generate(self, n, m, k, evaluation, options):
py_k = k.get_int_value()
is_directed = has_directed_option(options)

with RandomEnv(evaluation) as rand:
with RandomEnv(evaluation) as _:
for _ in range(py_k):
# seed = rand.randint(0, 2 ** 63 - 1) # 2**63 is too large
G = nx.gnm_random_graph(py_n, py_m, directed=is_directed)
Expand Down
3 changes: 1 addition & 2 deletions pymathics/graph/version.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-


# This file is suitable for sourcing inside POSIX shell as
# well as importing into Python. That's why there is no
# space around "=" below.
# fmt: off
__version__="2.3.1.dev0"
__version__="5.0.0.dev0"

0 comments on commit e808963

Please sign in to comment.