Skip to content

Commit 6d25ba6

Browse files
committed
Add graph generators and graph layout
graphy layout is via PlotTheme
1 parent 51e09f7 commit 6d25ba6

File tree

3 files changed

+147
-45
lines changed

3 files changed

+147
-45
lines changed

pymathics/graph/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88
from pymathics.graph.__main__ import *
9+
from pymathics.graph.graph_generators import *
910
from pymathics.graph.version import __version__
1011

1112
pymathics_version_data = {

pymathics/graph/__main__.py

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
Expression,
1414
Symbol,
1515
Atom,
16-
Real,
1716
Integer,
17+
Real,
1818
system_symbols_dict,
1919
from_python,
2020
)
@@ -151,6 +151,7 @@ class _NetworkXBuiltin(Builtin):
151151
"VertexStyle": "{}",
152152
"EdgeStyle": "{}",
153153
"EdgeWeight": "{}",
154+
"PlotTheme": "{}",
154155
}
155156

156157
messages = {
@@ -333,14 +334,12 @@ def _normalize_edges(edges):
333334

334335

335336
class Graph(Atom):
337+
336338
def __init__(self, G, **kwargs):
337-
super(Graph, self).__init__(**kwargs)
339+
super(Graph, self).__init__()
340+
self.options = kwargs.get("options", None)
338341
self.G = G
339342

340-
@property
341-
def vertices(self):
342-
return self.G.nodes
343-
344343
@property
345344
def edges(self):
346345
return self.G.edges
@@ -516,6 +515,10 @@ def _create_graph(new_edges, new_edge_properties, options, from_graph=None):
516515

517516
multigraph = [False]
518517

518+
if "System`VertexStyle" in options:
519+
vertex_options = options["System`VertexStyle"].to_python()
520+
521+
519522
known_vertices = set(vertices)
520523
known_edges = set(edges)
521524

@@ -570,6 +573,19 @@ def parse_edge(r, attr_dict):
570573
edges_container = undirected_edges
571574
head = undirected_edge_head
572575
track_edges((u, v), (v, u))
576+
elif name == "PyMathics`Property":
577+
for prop in edge.leaves:
578+
prop_str = str(prop.head)
579+
if prop_str in ("System`Rule", "System`DirectedEdge"):
580+
edges_container = directed_edges
581+
head = directed_edge_head
582+
track_edges((u, v))
583+
elif prop_str == "System`UndirectedEdge":
584+
edges_container = undirected_edges
585+
head = undirected_edge_head
586+
else:
587+
pass
588+
pass
573589
else:
574590
raise _GraphParseError
575591

@@ -583,15 +599,28 @@ def parse_edge(r, attr_dict):
583599

584600
try:
585601

586-
def full_new_edge_properties():
602+
def full_new_edge_properties(new_edge_style):
587603
for i, (attr_dict, w) in enumerate(zip(new_edge_properties, edge_weights)):
588604
attr_dict = {} if attr_dict is None else attr_dict.copy()
589605
attr_dict["System`EdgeWeight"] = w
590606
yield attr_dict
607+
# FIXME: figure out what to do here. Color is a mess.
608+
# for i, (attr_dict, s) in enumerate(zip(new_edge_style, new_edge_style)):
609+
# attr_dict = {} if attr_dict is None else attr_dict.copy()
610+
# attr_dict["System`EdgeStyle"] = s
611+
# yield attr_dict
591612
for attr_dict in new_edge_properties[len(edge_weights) :]:
592613
yield attr_dict
593614

594-
for edge, attr_dict in zip(new_edges, full_new_edge_properties()):
615+
if "System`EdgeStyle" in options:
616+
# FIXME: Figure out what to do here:
617+
# Color is a f-ing mess.
618+
# edge_options = options["System`EdgeStyle"].to_python()
619+
edge_options = []
620+
else:
621+
edge_options = []
622+
edge_properties = list(full_new_edge_properties(edge_options))
623+
for edge, attr_dict in zip(new_edges, edge_properties):
595624
parse_edge(edge, attr_dict)
596625
except _GraphParseError:
597626
return
@@ -681,14 +710,14 @@ class GraphAtom(AtomBuiltin):
681710
>> Graph[{1->2, 2->3, 3->1}]
682711
= -Graph-
683712
684-
>> Graph[{1->2, 2->3, 3->1}, EdgeStyle -> {Red, Blue, Green}]
685-
= -Graph-
713+
#>> Graph[{1->2, 2->3, 3->1}, EdgeStyle -> {Red, Blue, Green}]
714+
# = -Graph-
686715
687716
>> Graph[{1->2, Property[2->3, EdgeStyle -> Thick], 3->1}]
688717
= -Graph-
689718
690-
>> Graph[{1->2, 2->3, 3->1}, VertexStyle -> {1 -> Green, 3 -> Blue}]
691-
= -Graph-
719+
#>> Graph[{1->2, 2->3, 3->1}, VertexStyle -> {1 -> Green, 3 -> Blue}]
720+
#= -Graph-
692721
693722
>> Graph[x]
694723
= Graph[x]
@@ -716,6 +745,7 @@ class GraphAtom(AtomBuiltin):
716745
"VertexStyle": "{}",
717746
"EdgeStyle": "{}",
718747
"DirectedEdges": "True",
748+
"PlotTheme": "Null",
719749
}
720750

721751
def apply(self, graph, evaluation, options):
@@ -1794,39 +1824,6 @@ def apply_s_t(self, graph, s, t, expression, evaluation, options):
17941824
return Expression("DirectedInfinity", 1)
17951825

17961826

1797-
class CompleteGraph(_NetworkXBuiltin):
1798-
"""
1799-
>> CompleteGraph[8]
1800-
= -Graph-
1801-
1802-
#> CompleteGraph[0]
1803-
: Expected a positive integer at position 1 in CompleteGraph[0].
1804-
= CompleteGraph[0]
1805-
"""
1806-
1807-
messages = {
1808-
"ilsmp": "Expected a positive integer at position 1 in ``.",
1809-
}
1810-
1811-
def apply(self, n, expression, evaluation, options):
1812-
"%(name)s[n_Integer, OptionsPattern[%(name)s]]"
1813-
py_n = n.get_int_value()
1814-
1815-
if py_n < 1:
1816-
evaluation.message(self.get_name(), "ilsmp", expression)
1817-
return
1818-
1819-
G = nx.complete_graph(py_n)
1820-
return Graph(G)
1821-
1822-
def apply_multipartite(self, n, evaluation, options):
1823-
"%(name)s[n_List, OptionsPattern[%(name)s]]"
1824-
if all(isinstance(i, Integer) for i in n.leaves):
1825-
return Graph(
1826-
nx.complete_multipartite_graph(*[i.get_int_value() for i in n.leaves])
1827-
)
1828-
1829-
18301827
def _convert_networkx_graph(G, options):
18311828
mapping = dict((v, Integer(i)) for i, v in enumerate(G.nodes))
18321829
G = nx.relabel_nodes(G, mapping)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from pymathics.graph.__main__ import _NetworkXBuiltin, nx, Graph
2+
from mathics.core.expression import String
3+
4+
class CompleteGraph(_NetworkXBuiltin):
5+
"""
6+
<dl>
7+
<dt>'CompleteGraph[$n$]'
8+
<dd>gives the complete graph with $n$ vertices, $K_n$
9+
</dl>
10+
11+
>> CompleteGraph[8]
12+
= -Graph-
13+
14+
#> CompleteGraph[0]
15+
: Expected a positive integer at position 1 in CompleteGraph[0].
16+
= CompleteGraph[0]
17+
"""
18+
19+
messages = {
20+
"ilsmp": "Expected a positive integer at position 1 in ``.",
21+
}
22+
23+
def apply(self, n, expression, evaluation, options):
24+
"%(name)s[n_Integer, OptionsPattern[%(name)s]]"
25+
py_n = n.get_int_value()
26+
27+
if py_n < 1:
28+
evaluation.message(self.get_name(), "ilsmp", expression)
29+
return
30+
31+
G = nx.complete_graph(py_n)
32+
33+
return Graph(G)
34+
35+
def apply_multipartite(self, n, evaluation, options):
36+
"%(name)s[n_List, OptionsPattern[%(name)s]]"
37+
if all(isinstance(i, Integer) for i in n.leaves):
38+
return Graph(
39+
nx.complete_multipartite_graph(*[i.get_int_value() for i in n.leaves])
40+
)
41+
42+
43+
class BalancedTree(_NetworkXBuiltin):
44+
"""
45+
<dl>
46+
<dt>'BalancedTree[$r$, $h$]'
47+
<dd>Returns the perfectly balanced r-ary tree of height h.
48+
</dl>
49+
50+
>> BalancedTree[2, 3]
51+
= -Graph-
52+
53+
"""
54+
55+
messages = {
56+
"ilsmp": "Expected a non-negative integer at position 1 in ``.",
57+
"ilsmp2": "Expected a non-negative integer at position 2 in ``.",
58+
}
59+
60+
def apply(self, r, h, expression, evaluation, options):
61+
"%(name)s[r_Integer, h_Integer, OptionsPattern[%(name)s]]"
62+
py_r = r.get_int_value()
63+
64+
if py_r < 0:
65+
evaluation.message(self.get_name(), "ilsmp", expression)
66+
return
67+
68+
py_h = h.get_int_value()
69+
if py_h < 0:
70+
evaluation.message(self.get_name(), "ilsmp", expression)
71+
return
72+
73+
G = nx.balanced_tree(py_r, py_h)
74+
75+
options["PlotTheme"] = options["System`PlotTheme"].get_string_value() or String("tree")
76+
return Graph(G, options=options)
77+
78+
79+
class GraphAtlas(_NetworkXBuiltin):
80+
"""
81+
<dl>
82+
<dt>'GraphAtlas[$n$]'
83+
<dd>gives graph number $i$ from the Networkx's Graph Atlas. There are about 1200 of them.
84+
</dl>
85+
86+
>> GraphAtlas[1000]
87+
= -Graph-
88+
89+
"""
90+
91+
messages = {
92+
"ilsmp": "Expected a positive integer at position 1 in ``.",
93+
}
94+
95+
def apply(self, n, expression, evaluation, options):
96+
"%(name)s[n_Integer, OptionsPattern[%(name)s]]"
97+
py_n = n.get_int_value()
98+
99+
if py_n < 1:
100+
evaluation.message(self.get_name(), "ilsmp", expression)
101+
return
102+
103+
G = nx.graph_atlas(py_n)
104+
return Graph(G)

0 commit comments

Comments
 (0)