Skip to content

Commit 90d6767

Browse files
authored
Plot structure tests (#1514)
This is the first of a series of PRs aiming to improve plotting performance for Plot3D (initially): * This PR improves Plot3D testing to look at the structure of the resulting Graphics3D expression, improving confidence that nothing is broken by subsequent refactoring. * Refactor eval_Plot3D to separate the option processing stuff from the generation of the Graphics3D output in preparation for the subsequent step. This should cause no functional change, and the improved testing introduced by the previous PR will increase confidence that that is the case. * Introduce optional (for now) generation of Graphics3D output using GraphicsComplex and NumericArray in Plot3D. This should provide greatly improved plotting performance for consumers who enable the option and are prepared to consume GraphicsComplex. * Subsequent PRs may extend this to other types of plots, update internal (core) consumers of Graphics3D to understand GraphicsComplex, etc.
1 parent e51a26d commit 90d6767

File tree

2 files changed

+153
-1
lines changed

2 files changed

+153
-1
lines changed

mathics/core/util.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from platform import python_implementation
1010
from typing import Optional
1111

12+
from mathics.core.symbols import Symbol
13+
1214
IS_PYPY = python_implementation() == "PyPy"
1315

1416

@@ -113,3 +115,29 @@ def subranges(
113115
items[start : start + length],
114116
(items[:start], items[start + length :]),
115117
)
118+
119+
120+
def print_expression_tree(expr, indent="", marker=lambda expr: ""):
121+
"""
122+
Print a Mathics Expression as an indented tree.
123+
Caller may supply a marker function that computes a marker
124+
to be displayed in the tree for the given node.
125+
"""
126+
if isinstance(expr, Symbol):
127+
print(f"{indent}{marker(expr)}{expr}")
128+
elif not hasattr(expr, "elements"):
129+
print(f"{indent}{marker(expr)}{expr.get_head()} {expr}")
130+
else:
131+
print(f"{indent}{marker(expr)}{expr.head}")
132+
for elt in expr.elements:
133+
print_expression_tree(elt, indent + " ", marker=marker)
134+
135+
136+
def print_sympy_tree(expr, indent=""):
137+
"""Print a SymPy Expression as an indented tree"""
138+
if expr.args:
139+
print(f"{indent}{expr.func.__name__}")
140+
for i, arg in enumerate(expr.args):
141+
print_sympy_tree(arg, indent + " ")
142+
else:
143+
print(f"{indent}{expr.func.__name__}({str(expr)})")

test/builtin/drawing/test_plot.py

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
Unit tests from mathics.builtin.drawing.plot
44
"""
55

6-
from test.helper import check_evaluation
6+
from test.helper import check_evaluation, session
77

88
import pytest
99

10+
from mathics.core.util import print_expression_tree
11+
1012

1113
def test__listplot():
1214
"""tests for module builtin.drawing.plot._ListPlot"""
@@ -201,3 +203,125 @@ def test_plot(str_expr, msgs, str_expected, fail_msg):
201203
failure_message=fail_msg,
202204
expected_messages=msgs,
203205
)
206+
207+
208+
#
209+
# Call plotting functions and examine the structure of the output
210+
# In case of error trees are printed with an embedded >>> marker showing location of error
211+
#
212+
213+
214+
def print_expression_tree_with_marker(expr):
215+
print_expression_tree(expr, marker=lambda expr: getattr(expr, "_marker", ""))
216+
217+
218+
def check_structure(result, expected):
219+
"""Check that expected is a prefix of result at every node"""
220+
221+
def error(msg):
222+
result._marker = "RESULT >>> "
223+
expected._marker = "EXPECTED >>> "
224+
raise AssertionError(msg)
225+
226+
# do the heads match?
227+
if result.get_head() != expected.get_head():
228+
error("heads don't match")
229+
230+
# does the structure match?
231+
if hasattr(expected, "elements"):
232+
if not hasattr(result, "elements"):
233+
error("expected elements but result has none")
234+
for i, e in enumerate(expected.elements):
235+
if len(result.elements) <= i:
236+
error("result has too few elements")
237+
check_structure(result.elements[i], e)
238+
else:
239+
if str(result) != str(expected):
240+
error("leaves don't match")
241+
242+
243+
def eval_and_check_structure(str_expr, str_expected):
244+
expr = session.parse(str_expr)
245+
result = expr.evaluate(session.evaluation)
246+
expected = session.parse(str_expected)
247+
try:
248+
check_structure(result, expected)
249+
except AssertionError as oops:
250+
print(f"\nERROR: {oops} (error is marked with >>> below)")
251+
print("=== result:")
252+
print_expression_tree_with_marker(result)
253+
print("=== expected:")
254+
print_expression_tree_with_marker(expected)
255+
raise
256+
257+
258+
def test_plot3d_default():
259+
eval_and_check_structure(
260+
"""
261+
Plot3D[
262+
x+y,
263+
{x,0,1}, {y,0,1},
264+
PlotPoints->{2,2},
265+
MaxRecursion->0
266+
]
267+
""",
268+
"""
269+
Graphics3D[
270+
{
271+
Polygon[{{0.0,0.0,0.0}, {0.0,0.5,0.5}, {0.5,0.0,0.5}}],
272+
Polygon[{{}}]
273+
},
274+
AspectRatio -> 1,
275+
Axes -> True,
276+
AxesStyle -> {},
277+
Background -> Automatic,
278+
BoxRatios -> {1, 1, 0.4},
279+
ImageSize -> Automatic,
280+
LabelStyle -> {},
281+
PlotRange -> Automatic,
282+
PlotRangePadding -> Automatic,
283+
TicksStyle -> {}
284+
]
285+
""",
286+
)
287+
288+
289+
def test_plot3d_nondefault():
290+
eval_and_check_structure(
291+
"""
292+
Plot3D[
293+
x+y,
294+
{x,0,1}, {y,0,1},
295+
PlotPoints->{2,2},
296+
MaxRecursion->0
297+
AspectRatio -> 0.5,
298+
Axes -> False,
299+
AxesStyle -> {Red,Blue},
300+
Background -> Green,
301+
BoxRatios -> {10, 10, 1},
302+
ImageSize -> {200,200},
303+
LabelStyle -> Red,
304+
PlotRange -> {0,1},
305+
PlotRangePadding -> {1,2},
306+
TicksStyle -> {Purple,White}
307+
]
308+
""",
309+
"""
310+
Graphics3D[
311+
{
312+
Polygon[{{0.0,0.0,0.0}, {0.0,0.5,0.5}, {0.5,0.0,0.5}}],
313+
Polygon[{{}}]
314+
},
315+
AspectRatio -> 1, (* TODO: is not passed through apparently - or is my misunderstanding? *)
316+
Axes -> False,
317+
AxesStyle -> {RGBColor[1,0,0],RGBColor[0,0,1]},
318+
Background -> RGBColor[0,1,0],
319+
BoxRatios -> {10, 10, 1},
320+
ImageSize -> {200,200},
321+
LabelStyle -> RGBColor[1,0,0],
322+
PlotRange -> {0,1},
323+
PlotRangePadding -> {1,2},
324+
TicksStyle -> {RGBColor[0.5,0,0.5],GrayLevel[1]}
325+
]
326+
""",
327+
)

0 commit comments

Comments
 (0)