Skip to content

Commit 3050dc8

Browse files
committed
Style on tree; add 1st tree pytest
1 parent c084ef3 commit 3050dc8

6 files changed

Lines changed: 165 additions & 17 deletions

File tree

pymathics/graph/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
from mathics.builtin.base import AtomBuiltin, Builtin
1616
from mathics.builtin.box.graphics import GraphicsBox
17-
from mathics.core.atoms import Integer, Integer0, Integer1, Real
17+
from mathics.core.atoms import Atom, Integer, Integer0, Integer1, Real
1818
from mathics.core.convert.expression import ListExpression, from_python
19-
from mathics.core.expression import Atom, Expression
19+
from mathics.core.expression import Expression
2020
from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue
2121
from mathics.core.systemsymbols import (
2222
SymbolBlank,

pymathics/graph/tree.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import networkx as nx
2-
from mathics.core.expression import Atom, Symbol
2+
from mathics.core.atoms import Atom
3+
from mathics.core.evaluation import Evaluation
4+
from mathics.core.symbols import SymbolConstant, SymbolFalse, SymbolTrue
35

46
from pymathics.graph.__main__ import (
57
DEFAULT_GRAPH_OPTIONS,
@@ -16,37 +18,43 @@
1618
from mathics.builtin.base import AtomBuiltin
1719

1820

19-
class TreeGraphAtom(AtomBuiltin):
21+
def eval_TreeGraphQ(g: Graph) -> SymbolConstant:
2022
"""
21-
>> TreeGraph[{1->2, 2->3, 3->1}]
22-
= -Graph-
23-
23+
Returns SymbolTrue if g is a (networkx) tree and SymbolFalse
24+
otherwise.
2425
"""
26+
if not isinstance(g, Graph):
27+
return SymbolFalse
28+
return SymbolTrue if nx.is_tree(g.G) else SymbolFalse
2529

30+
31+
# FIXME: do we need to have TreeGraphAtom and TreeGraph?
32+
# Can't these be combined into one?
33+
class TreeGraphAtom(AtomBuiltin):
2634
options = DEFAULT_TREE_OPTIONS
2735

2836
messages = {
2937
"v": "Expected first parameter vertices to be a list of vertices",
3038
"notree": "Graph is not a tree.",
3139
}
3240

33-
def eval(self, rules, evaluation, options):
41+
def eval(self, rules, evaluation: Evaluation, options: dict):
3442
"TreeGraph[rules_List, OptionsPattern[%(name)s]]"
35-
g = _graph_from_list(rules.leaves, options)
43+
g = _graph_from_list(rules.elements, options)
3644
if not nx.is_tree(g.G):
3745
evaluation.message(self.get_name(), "notree")
3846

3947
g.G.graph_layout = "tree"
4048
# Compute/check/set for root?
4149
return g
4250

43-
def eval_with_v_e(self, vertices, edges, evaluation, options):
51+
def eval_with_v_e(self, vertices, edges, evaluation: Evaluation, options: dict):
4452
"TreeGraph[vertices_List, edges_List, OptionsPattern[%(name)s]]"
45-
if not all(isinstance(v, Atom) for v in vertices.leaves):
53+
if not all(isinstance(v, Atom) for v in vertices.elements):
4654
evaluation.message(self.get_name(), "v")
4755

4856
g = _graph_from_list(
49-
edges.leaves, options=options, new_vertices=vertices.leaves
57+
edges.elements, options=options, new_vertices=vertices.elements
5058
)
5159
if not nx.is_tree(g.G):
5260
evaluation.message(self.get_name(), "notree")
@@ -57,6 +65,12 @@ def eval_with_v_e(self, vertices, edges, evaluation, options):
5765

5866

5967
class TreeGraph(Graph):
68+
"""
69+
>> TreeGraph[{1->2, 2->3, 2->4}]
70+
= -Graph-
71+
72+
"""
73+
6074
options = DEFAULT_TREE_OPTIONS
6175

6276
messages = {
@@ -85,8 +99,8 @@ class TreeGraphQ(_NetworkXBuiltin):
8599
= False
86100
"""
87101

88-
def eval(self, g, expression, evaluation, options):
102+
def eval(
103+
self, g, expression, evaluation: Evaluation, options: dict
104+
) -> SymbolConstant:
89105
"TreeGraphQ[g_, OptionsPattern[%(name)s]]"
90-
if not isinstance(g, Graph):
91-
return Symbol("False")
92-
return Symbol("True" if nx.is_tree(g.G) else "False")
106+
return eval_TreeGraphQ(g)

test/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-

test/helper.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# -*- coding: utf-8 -*-
2+
import time
3+
from typing import Optional
4+
5+
from mathics.session import MathicsSession
6+
7+
# Set up a Mathics session with definitions.
8+
# For consistency set the character encoding ASCII which is
9+
# the lowest common denominator available on all systems.
10+
session = MathicsSession(character_encoding="ASCII")
11+
12+
13+
def reset_session(add_builtin=True, catch_interrupt=False):
14+
global session
15+
session.reset()
16+
17+
18+
def evaluate_value(str_expr: str):
19+
return session.evaluate(str_expr).value
20+
21+
22+
def evaluate(str_expr: str):
23+
return session.evaluate(str_expr)
24+
25+
26+
def check_evaluation(
27+
str_expr: str,
28+
str_expected: str,
29+
failure_message: str = "",
30+
hold_expected: bool = False,
31+
to_string_expr: bool = True,
32+
to_string_expected: bool = True,
33+
to_python_expected: bool = False,
34+
expected_messages: Optional[tuple] = None,
35+
):
36+
"""
37+
Helper function to test Mathics expression against
38+
its results
39+
40+
Compares the expressions represented by ``str_expr`` and ``str_expected`` by evaluating
41+
the first, and optionally, the second.
42+
43+
to_string_expr: If ``True`` (default value) the result of the evaluation is converted
44+
into a Python string. Otherwise, the expression is kept as an Expression
45+
object. If this argument is set to ``None``, the session is reset.
46+
47+
failure_message (str): message shown in case of failure
48+
hold_expected (bool): If ``False`` (default value) the ``str_expected`` is evaluated. Otherwise,
49+
the expression is considered literally.
50+
51+
to_string_expected: If ``True`` (default value) the expected expression is
52+
evaluated and then converted to a Python string. result of the evaluation is converted
53+
into a Python string. If ``False``, the expected expression is kept as an Expression object.
54+
55+
to_python_expected: If ``True``, and ``to_string_expected`` is ``False``, the result of evaluating ``str_expr``
56+
is compared against the result of the evaluation of ``str_expected``, converted into a
57+
Python object.
58+
59+
expected_messages ``Optional[tuple[str]]``: If a tuple of strings are passed into this parameter, messages and prints raised during
60+
the evaluation of ``str_expr`` are compared with the elements of the list. If ``None``, this comparison
61+
is ommited.
62+
"""
63+
if str_expr is None:
64+
reset_session()
65+
return
66+
67+
if to_string_expr:
68+
str_expr = f"ToString[{str_expr}]"
69+
result = evaluate_value(str_expr)
70+
else:
71+
result = evaluate(str_expr)
72+
73+
outs = [out.text for out in session.evaluation.out]
74+
75+
if to_string_expected:
76+
if hold_expected:
77+
expected = str_expected
78+
else:
79+
str_expected = f"ToString[{str_expected}]"
80+
expected = evaluate_value(str_expected)
81+
else:
82+
if hold_expected:
83+
if to_python_expected:
84+
expected = str_expected
85+
else:
86+
expected = evaluate(f"HoldForm[{str_expected}]").elements[0]
87+
else:
88+
expected = evaluate(str_expected)
89+
if to_python_expected:
90+
expected = expected.to_python(string_quotes=False)
91+
92+
print(time.asctime())
93+
if failure_message:
94+
print((result, expected))
95+
assert result == expected, failure_message
96+
else:
97+
print((result, expected))
98+
assert result == expected
99+
100+
if expected_messages is not None:
101+
msgs = list(expected_messages)
102+
expected_len = len(msgs)
103+
got_len = len(outs)
104+
assert (
105+
expected_len == got_len
106+
), f"expected {expected_len}; got {got_len}. Messages: {outs}"
107+
for (out, msg) in zip(outs, msgs):
108+
if out != msg:
109+
print(f"out:<<{out}>>")
110+
print(" and ")
111+
print(f"expected=<<{msg}>>")
112+
assert False, " do not match."

test/test_algorithms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
"""
3-
Unit tests for mathics.builtins.numbers.algebra
3+
Unit tests for pymathics.graph.algorithms
44
"""
55
from test.helper import check_evaluation, evaluate, evaluate_value
66

test/test_tree.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Unit tests for pymathics.graph.tree
4+
"""
5+
from test.helper import check_evaluation, evaluate_value
6+
7+
8+
def setup_module(module):
9+
"""Load pymathics.graph"""
10+
assert evaluate_value('LoadModule["pymathics.graph"]') == "pymathics.graph"
11+
12+
13+
def test_tree():
14+
for str_expr, str_expected in [
15+
("TreeGraphQ[StarGraph[3]]", "True"),
16+
("TreeGraphQ[CompleteGraph[0]]", "False"),
17+
("TreeGraphQ[CompleteGraph[1]]", "True"),
18+
("TreeGraphQ[CompleteGraph[2]]", "True"),
19+
("TreeGraphQ[CompleteGraph[3]]", "False"),
20+
]:
21+
check_evaluation(str_expr, str_expected)

0 commit comments

Comments
 (0)