Skip to content

Commit 275da40

Browse files
mmaterarocky
andauthored
Improving create graph (#10)
* fix Characterencoding * fixes in creating graph * importing unlisted symbols * KatzCentrality --------- Co-authored-by: rocky <[email protected]>
1 parent 32c16e5 commit 275da40

File tree

5 files changed

+127
-72
lines changed

5 files changed

+127
-72
lines changed

Makefile

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ PIP ?= $(PYTHON) -m pip
1010
RM ?= rm
1111

1212

13-
14-
1513
.PHONY: all build \
1614
check clean \
1715
develop dist doc doc-data \
@@ -54,7 +52,7 @@ clean-pyc:
5452

5553
#: Run py.test tests. Use environment variable "o" for pytest options
5654
pytest:
57-
$(PYTHON) -m pytest test $o
55+
MATHICS_CHARACTER_ENCODING="ASCII" $(PYTHON) -m pytest $(PYTEST_WORKERS) test $o
5856

5957

6058
# #: Create data that is used to in Django docs and to build TeX PDF

pymathics/graph/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"""
1515

1616
from pymathics.graph.base import (
17+
AdjacencyList,
1718
AcyclicGraphQ,
1819
BetweennessCentrality,
1920
ClosenessCentrality,
@@ -69,6 +70,7 @@
6970
CycleGraph,
7071
FullRAryTree,
7172
GraphAtlas,
73+
GraphData,
7274
HknHararyGraph,
7375
HmnHararyGraph,
7476
KaryTree,
@@ -91,6 +93,7 @@
9193
# Thsee are the publicly exported names
9294
__all__ = [
9395
"AcyclicGraphQ",
96+
"AdjacencyList",
9497
"BalancedTree",
9598
"BarbellGraph",
9699
"BetweennessCentrality",
@@ -118,6 +121,7 @@
118121
"GraphAtlas",
119122
"GraphAtom",
120123
"GraphBox",
124+
"GraphData",
121125
"GraphDistance",
122126
"HITSCentrality",
123127
"HighlightGraph",

pymathics/graph/base.py

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ class _FullGraphRewrite(Exception):
423423

424424
def _normalize_edges(edges):
425425
for edge in edges:
426-
if edge.has_form("Property", 2):
426+
if edge.has_form("Pymathics`Property", 2):
427427
expr, prop = edge.elements
428428
yield Expression(edge.get_head(), list(_normalize_edges([expr]))[0], prop)
429429
elif edge.get_head_name() == "System`Rule":
@@ -726,7 +726,8 @@ def _graph_from_list(rules, options, new_vertices=None):
726726
def _create_graph(
727727
new_edges, new_edge_properties, options, from_graph=None, new_vertices=None
728728
):
729-
729+
vertices_dict = {}
730+
# Classification of vertex and edges
730731
known_vertices = set()
731732
vertices = []
732733
vertex_properties = []
@@ -745,9 +746,6 @@ def add_vertex(x, attr_dict=None):
745746
directed_edges = []
746747
undirected_edges = []
747748

748-
if new_vertices is not None:
749-
vertices = [add_vertex(v) for v in new_vertices]
750-
751749
if from_graph is not None:
752750
old_vertices = dict(from_graph.nodes.data())
753751
vertices += old_vertices
@@ -767,6 +765,31 @@ def add_vertex(x, attr_dict=None):
767765

768766
multigraph = [False]
769767

768+
769+
if new_vertices is not None:
770+
for v in new_vertices:
771+
add_vertex(v)
772+
773+
def add_vertex(x, attr_dict=None):
774+
if attr_dict is None:
775+
attr_dict = {}
776+
if x.has_form("Pymathics`Property", 2):
777+
expr, prop = x.elements
778+
attr_dict.update(_parse_property(prop, attr_dict))
779+
return add_vertex(expr, attr_dict)
780+
elif x not in known_vertices:
781+
known_vertices.add(x)
782+
vertices.append(x)
783+
vertex_properties.append(attr_dict)
784+
vertices_dict[x] = attr_dict
785+
else:
786+
vertices_dict[x].update(attr_dict)
787+
return x
788+
789+
if new_vertices is not None:
790+
for v in new_vertices:
791+
add_vertex(v)
792+
770793
known_edges = set(edges)
771794
# It is simpler to just recompute this than change the above to work
772795
# incrementally
@@ -788,12 +811,28 @@ def track_edges(*edges):
788811
SymbolDirectedEdge if use_directed_edges else SymbolUndirectedEdge
789812
)
790813

791-
def parse_edge(r, attr_dict):
814+
def parse_edge(r, attr_dict=None):
815+
if attr_dict is None:
816+
attr_dict = {}
817+
792818
if isinstance(r, Atom):
793819
raise _GraphParseError(
794820
msg=f"{r} is an atom, and hence does not define an edge."
795821
)
796822

823+
if r.has_form("Pymathics`Property", None):
824+
expr, prop = r.elements
825+
attr_dict.update(_parse_property(prop, attr_dict))
826+
return parse_edge(expr, attr_dict)
827+
828+
829+
if r.head not in (SymbolRule, SymbolDirectedEdge, SymbolUndirectedEdge):
830+
raise _GraphParseError(
831+
msg=f"{r} is not an edge description."
832+
)
833+
834+
835+
797836
name = r.get_head_name()
798837
elements = r.elements
799838

@@ -1138,7 +1177,6 @@ class DegreeCentrality(_Centrality):
11381177

11391178
def _from_dict(self, graph, centrality):
11401179
s = len(graph.G) - 1 # undo networkx's normalization
1141-
print("_from_dict", (graph, type(graph)))
11421180
return ListExpression(
11431181
*[Integer(s * centrality.get(v, 0)) for v in graph.vertices],
11441182
)
@@ -1274,7 +1312,6 @@ def eval(self, graph, expression, evaluation, options):
12741312
if graph:
12751313

12761314
def rules():
1277-
print("Looking for Edge rules")
12781315
for edge in graph.edges:
12791316
u, v = edge
12801317
yield Expression(SymbolRule, u, v)
@@ -1566,7 +1603,7 @@ class KatzCentrality(_ComponentwiseCentrality):
15661603
"""
15671604

15681605
rules = {
1569-
"KatzCentrality[g_, alpha_]": "KatzCentrality[g, alpha, 1]",
1606+
"Pymathics`KatzCentrality[g_, Pymathics`alpha_]": "Pymathics`KatzCentrality[g, Pymathics`alpha, 1]",
15701607
}
15711608

15721609
def _centrality(self, g, weight, alpha, beta):
@@ -1575,11 +1612,16 @@ def _centrality(self, g, weight, alpha, beta):
15751612
)
15761613

15771614
def eval(self, graph, alpha, beta, expression, evaluation, options):
1578-
"%(name)s[graph_, alpha_, beta_, OptionsPattern[%(name)s]]"
1615+
"Pymathics`KatzCentrality[graph_, alpha_, beta_, OptionsPattern[%(name)s]]"
15791616
graph = self._build_graph(graph, evaluation, options, expression)
1617+
print("Katz graph", graph)
15801618
if graph:
1581-
py_alpha = alpha.to_mpmath()
1582-
py_beta = beta.to_mpmath()
1619+
print([alpha, beta, type(alpha), type(beta)])
1620+
try:
1621+
py_alpha = alpha.to_mpmath()
1622+
py_beta = beta.to_mpmath()
1623+
except NotImplementedError:
1624+
return
15831625
if py_alpha is None or py_beta is None:
15841626
return
15851627
return self._compute(
@@ -1639,6 +1681,7 @@ def eval(self, graph, expression, evaluation, options):
16391681
graph = self._build_graph(graph, evaluation, options, expression, quiet=True)
16401682
if graph:
16411683
return from_python(graph.is_mixed_graph())
1684+
return SymbolFalse
16421685

16431686

16441687
class MultigraphQ(_NetworkXBuiltin):
@@ -1757,14 +1800,21 @@ class PropertyValue(Builtin):
17571800

17581801
def eval(self, graph, item, name, evaluation):
17591802
"PropertyValue[{graph_Graph, item_}, name_Symbol]"
1803+
name_str = name.get_name()
17601804
if isinstance(graph, Graph):
1761-
try:
1762-
# Fixme: this does not work...
1763-
value = graph.G.get_property(item, name.get_name())
1764-
except:
1765-
return SymbolFailed
1766-
if value is None:
1805+
if (
1806+
item.has_form("Rule", 2)
1807+
or item.has_form("DirectedEdge", 2)
1808+
or item.has_form("UndirectedEdge", 2)
1809+
):
1810+
item_g = graph.G.edges().get(item.elements)
1811+
else:
1812+
item_g = graph.G.nodes().get(item)
1813+
1814+
if item_g is None:
17671815
return SymbolFailed
1816+
1817+
value = item_g.get(name_str, SymbolFailed)
17681818
return value
17691819

17701820

pymathics/graph/generators.py

Lines changed: 52 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,17 @@ def graph_helper(
131131
return g
132132

133133

134+
135+
WL_TO_NETWORKX_FN = {
136+
"DodecahedralGraph": (nx.dodecahedral_graph, None),
137+
"DiamondGraph": (nx.diamond_graph, "spring"),
138+
"PappusGraph": (nx.pappus_graph, "circular"),
139+
"IsohedralGraph": (nx.icosahedral_graph, "spring"),
140+
"PetersenGraph": (nx.petersen_graph, None),
141+
}
142+
143+
144+
134145
class BalancedTree(_NetworkXBuiltin):
135146
"""
136147
<dl>
@@ -463,13 +474,47 @@ def eval(self, n, expression, evaluation: Evaluation, options: dict):
463474
return g
464475

465476

477+
class GraphData(_NetworkXBuiltin):
478+
"""
479+
<dl>
480+
<dt>'GraphData[$name$]'
481+
<dd>Returns a graph with the specified name.
482+
</dl>
483+
484+
>> GraphData["PappusGraph"]
485+
= -Graph-
486+
"""
487+
488+
def eval(self, name, expression, evaluation: Evaluation, options: dict) -> Graph:
489+
"Pymathics`GraphData[name_String, OptionsPattern[%(name)s]]"
490+
py_name = name.get_string_value()
491+
fn, layout = WL_TO_NETWORKX_FN.get(py_name, (None, None))
492+
if not fn:
493+
if not py_name.endswith("_graph"):
494+
py_name += "_graph"
495+
if py_name in ("LCF_graph", "make_small_graph"):
496+
# These graphs require parameters
497+
return
498+
import inspect
499+
500+
fn = dict(inspect.getmembers(nx, inspect.isfunction)).get(py_name, None)
501+
# parameters = inspect.signature(nx.diamond_graph).parameters.values()
502+
# if len([p for p in list(parameters) if p.kind in [inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD]]) != 0:
503+
# return
504+
if fn:
505+
g = graph_helper(fn, options, False, evaluation, layout)
506+
g.G.name = py_name
507+
return g
508+
509+
466510
class HknHararyGraph(_NetworkXBuiltin):
467-
"""<dl>
468-
<dt>'HmnHararyGraph[$k$, $n$]'
469-
<dd>Returns the Harary graph with given node connectivity and node number.
511+
"""
512+
<dl>
513+
<dt>'HmnHararyGraph[$k$, $n$]'
514+
<dd>Returns the Harary graph with given node connectivity and node number.
470515
471-
This second generator gives the Harary graph that minimizes the
472-
number of edges in the graph with given node connectivity and
516+
This second generator gives the Harary graph that minimizes the \
517+
number of edges in the graph with given node connectivity and \
473518
number of nodes.
474519
475520
Harary, F. The Maximum Connectivity of a Graph. Proc. Nat. Acad. Sci. USA 48, 1142-1146, 1962.
@@ -495,8 +540,8 @@ class HmnHararyGraph(_NetworkXBuiltin):
495540
<dt>'HmnHararyGraph[$m$, $n$]'
496541
<dd>Returns the Harary graph with given numbers of nodes and edges.
497542
498-
This generator gives the Harary graph that maximizes the node
499-
connectivity with given number of nodes and given number of
543+
This generator gives the Harary graph that maximizes the node \
544+
connectivity with given number of nodes and given number of \
500545
edges.
501546
502547
Harary, F. The Maximum Connectivity of a Graph. Proc. Nat. Acad. Sci. USA 48, 1142-1146, 1962.
@@ -740,43 +785,3 @@ def eval(
740785
return g
741786

742787

743-
WL_TO_NETWORKX_FN = {
744-
"DodecahedralGraph": (nx.dodecahedral_graph, None),
745-
"DiamondGraph": (nx.diamond_graph, "spring"),
746-
"PappusGraph": (nx.pappus_graph, "circular"),
747-
"IsohedralGraph": (nx.icosahedral_graph, "spring"),
748-
"PetersenGraph": (nx.petersen_graph, None),
749-
}
750-
751-
752-
class GraphData(_NetworkXBuiltin):
753-
"""
754-
<dl>
755-
<dt>'GraphData[$name$]'
756-
<dd>Returns a graph with the specified name.
757-
</dl>
758-
759-
>> GraphData["PappusGraph"]
760-
= -Graph-
761-
"""
762-
763-
def eval(self, name, expression, evaluation: Evaluation, options: dict) -> Graph:
764-
"%(name)s[name_String, OptionsPattern[%(name)s]]"
765-
py_name = name.get_string_value()
766-
fn, layout = WL_TO_NETWORKX_FN.get(py_name, (None, None))
767-
if not fn:
768-
if not py_name.endswith("_graph"):
769-
py_name += "_graph"
770-
if py_name in ("LCF_graph", "make_small_graph"):
771-
# These graphs require parameters
772-
return
773-
import inspect
774-
775-
fn = dict(inspect.getmembers(nx, inspect.isfunction)).get(py_name, None)
776-
# parameters = inspect.signature(nx.diamond_graph).parameters.values()
777-
# if len([p for p in list(parameters) if p.kind in [inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD]]) != 0:
778-
# return
779-
if fn:
780-
g = graph_helper(fn, options, False, evaluation, layout)
781-
g.G.name = py_name
782-
return g

test/helper.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44

55
from mathics.session import MathicsSession
66

7-
session = MathicsSession(add_builtin=True, catch_interrupt=False)
8-
9-
# Set up a Mathics3 session with definitions.
7+
# Set up a Mathics session with definitions.
108
# For consistency set the character encoding ASCII which is
119
# the lowest common denominator available on all systems.
12-
session = MathicsSession(character_encoding="ASCII")
10+
session = MathicsSession(add_builtin=True, catch_interrupt=False, character_encoding="ASCII")
1311

1412

1513
def reset_session(add_builtin=True, catch_interrupt=False):

0 commit comments

Comments
 (0)