Skip to content

Commit 4584080

Browse files
committed
adjust tests and context
1 parent a3af897 commit 4584080

File tree

8 files changed

+162
-43
lines changed

8 files changed

+162
-43
lines changed

pymathics/vectorizedplot/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,34 @@
2727
# The try block is needed because at installation time, dependencies are not
2828
# available. After the installation is successfull, we want to make this availabe.
2929
try:
30+
from pymathics.vectorizedplot.plot_plot import (
31+
LogPlot,
32+
ParametricPlot,
33+
Plot,
34+
PolarPlot,
35+
)
3036
from pymathics.vectorizedplot.plot_plot3d import (
37+
ComplexPlot,
38+
ComplexPlot3D,
3139
ContourPlot,
3240
ContourPlot3D,
41+
DensityPlot,
3342
ParametricPlot3D,
43+
Plot3D,
3444
SphericalPlot3D,
3545
)
3646

3747
_BUILTINS_ = (
48+
"ComplexPlot",
49+
"ComplexPlot3D",
3850
"ContourPlot",
3951
"ContourPlot3D",
52+
"DensityPlot",
53+
"LogPlot",
54+
"ParametricPlot",
55+
"PolarPlot",
56+
"Plot",
57+
"Plot3D",
4058
"ParametricPlot3D",
4159
"SphericalPlot3D",
4260
)
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""
2+
Vectorized evaluation routines for Plot and related subclasses of _Plot
3+
"""
4+
5+
import numpy as np
6+
from mathics.builtin.graphics import Graphics
7+
from mathics.builtin.options import filter_from_iterable, options_to_rules
8+
from mathics.core.convert.lambdify import lambdify_compile
9+
from mathics.core.element import BaseElement
10+
from mathics.core.evaluation import Evaluation
11+
from mathics.core.expression import Expression
12+
from mathics.core.symbols import SymbolList, strip_context
13+
from mathics.timing import Timer
14+
15+
from .colors import palette2, palette_color_directive
16+
from .util import GraphicsGenerator
17+
18+
19+
@Timer("eval_Plot_vectorized")
20+
def eval_Plot_vectorized(plot_options, options, evaluation: Evaluation):
21+
# Note on naming: we use t to refer to the independent variable initially.
22+
# For Plot etc. it will be x, but for ParametricPlot it is better called t,
23+
# and for PolarPlot theta. After we call the apply_function supplied by
24+
# the plotting class we will then have actual plot coordinate xs and ys.
25+
tname, tmin, tmax = plot_options.ranges[0]
26+
nt = plot_options.plot_points
27+
28+
# ParametricPlot passes a List of two functions, but lambdify_compile doesn't handle that
29+
# TODO: we should be receiving this as a Python list not an expression List?
30+
# TODO: can lambidfy_compile handle list with an appropriate to_sympy?
31+
def compile_maybe_list(evaluation, function, names):
32+
if isinstance(function, Expression) and function.head == SymbolList:
33+
fs = [lambdify_compile(evaluation, f, names) for f in function.elements]
34+
35+
def compiled(vs):
36+
return [f(vs) for f in fs]
37+
38+
else:
39+
compiled = lambdify_compile(evaluation, function, names)
40+
return compiled
41+
42+
# compile the functions
43+
with Timer("compile"):
44+
names = [strip_context(str(tname))]
45+
compiled_functions = [
46+
compile_maybe_list(evaluation, function, names)
47+
for function in plot_options.functions
48+
]
49+
50+
# compute requested regularly spaced points over the requested range
51+
ts = np.linspace(tmin, tmax, nt)
52+
53+
# 1-based indexes into point array to form a line
54+
line = np.arange(nt) + 1
55+
56+
# compute the curves and accumulate in a GraphicsGenerator
57+
graphics = GraphicsGenerator(dim=2)
58+
for i, function in enumerate(compiled_functions):
59+
# compute xs and ys from ts using the compiled function
60+
# and the apply_function supplied by the plot class
61+
with Timer("compute xs and ys"):
62+
xs, ys = plot_options.apply_function(function, ts)
63+
64+
# If the result is not numerical, we assume that the plot have failed.
65+
if isinstance(ys, BaseElement):
66+
return None
67+
68+
# sometimes expr gets compiled into something that returns a complex
69+
# even though the imaginary part is 0
70+
# TODO: check that imag is all 0?
71+
# assert np.all(np.isreal(zs)), "array contains complex values"
72+
xs = np.real(xs)
73+
ys = np.real(ys)
74+
75+
# take log if requested; downstream axes will adjust accordingly
76+
if plot_options.use_log_scale:
77+
ys = np.log10(ys)
78+
79+
# if it's a constant, make it a full array
80+
if isinstance(xs, (float, int, complex)):
81+
xs = np.full(ts.shape, xs)
82+
if isinstance(ys, (float, int, complex)):
83+
ys = np.full(ts.shape, ys)
84+
85+
# (nx, 2) array of points, to be indexed by lines
86+
xys = np.stack([xs, ys]).T
87+
88+
# give it a color from the 2d graph default color palette
89+
color = palette_color_directive(palette2, i)
90+
graphics.add_directives(color)
91+
92+
# emit this line
93+
graphics.add_complex(xys, lines=line, polys=None)
94+
95+
# copy options to output and generate the Graphics expr
96+
options = options_to_rules(options, filter_from_iterable(Graphics.options))
97+
graphics_expr = graphics.generate(options)
98+
return graphics_expr

pymathics/vectorizedplot/plot.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,6 @@ def to_list(expr):
556556
plot_range = [symbol_type("System`Automatic")] * dim
557557
plot_range[-1] = pr
558558
self.plot_range = plot_range
559-
560559
# ColorFunction and ColorFunctionScaling options
561560
# This was pulled from construct_density_plot (now eval_DensityPlot).
562561
# TODO: What does pop=True do? is it right?

pymathics/vectorizedplot/plot_plot.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
from mathics.core.symbols import SymbolTrue
1818
from mathics.core.systemsymbols import SymbolLogPlot, SymbolPlotRange, SymbolSequence
1919

20-
from pymathics.vectorizedplot.eval.drawing.plot import eval_Plot
21-
from pymathics.vectorizedplot.eval.drawing.plot_vectorized import eval_Plot_vectorized
20+
from pymathics.vectorizedplot.eval.plot_vectorized import eval_Plot_vectorized
2221

2322
from . import plot
2423

@@ -48,7 +47,7 @@ class _Plot(Builtin, ABC):
4847
"appropriate list of constraints."
4948
),
5049
}
51-
50+
context = "System`"
5251
options = Graphics.options.copy()
5352
options.update(
5453
{
@@ -84,8 +83,6 @@ def eval(self, functions, ranges, evaluation: Evaluation, options: dict):
8483
# because ndarray is unhashable, and in any case probably isn't useful
8584
# TODO: does caching results in the classic case have demonstrable performance benefit?
8685
apply_function = self.apply_function
87-
if not plot.use_vectorized_plot:
88-
apply_function = lru_cache(apply_function)
8986
plot_options.apply_function = apply_function
9087

9188
# TODO: PlotOptions has already regularized .functions to be a list
@@ -98,7 +95,7 @@ def eval(self, functions, ranges, evaluation: Evaluation, options: dict):
9895
plot_options.use_log_scale = self.use_log_scale
9996
plot_options.expect_list = self.expect_list
10097
if plot_options.plot_points is None:
101-
default_plot_points = 1000 if plot.use_vectorized_plot else 57
98+
default_plot_points = 1000
10299
plot_options.plot_points = default_plot_points
103100

104101
# pass through the expanded plot_range options
@@ -110,7 +107,7 @@ def eval(self, functions, ranges, evaluation: Evaluation, options: dict):
110107
options[str(SymbolLogPlot)] = SymbolTrue
111108

112109
# this will be either the vectorized or the classic eval function
113-
eval_function = eval_Plot_vectorized if plot.use_vectorized_plot else eval_Plot
110+
eval_function = eval_Plot_vectorized
114111
with np.errstate(all="ignore"): # suppress numpy warnings
115112
graphics = eval_function(plot_options, options, evaluation)
116113
return graphics
@@ -188,6 +185,7 @@ class Plot(_Plot):
188185
= -Graphics-
189186
"""
190187

188+
context = "System`"
191189
summary_text = "plot curves of one or more functions"
192190

193191
def apply_function(self, f: Callable, x_value):

pymathics/vectorizedplot/plot_plot3d.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class _Plot3D(Builtin):
3030
"""Common base class for Plot3D, DensityPlot, ComplexPlot, ComplexPlot3D"""
3131

3232
attributes = A_HOLD_ALL | A_PROTECTED
33+
context = "System`"
3334

3435
# Check for correct number of args
3536
eval_error = Builtin.generic_argument_error
@@ -84,10 +85,6 @@ class _Plot3D(Builtin):
8485
# 'MaxRecursion': '2', # FIXME causes bugs in svg output see #303
8586
}
8687

87-
def contribute(self, definitions, is_pymodule=False):
88-
print("contribute with ", type(self))
89-
super().contribute(definitions, is_pymodule)
90-
9188
def eval(
9289
self,
9390
functions,

test/helper.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,25 @@
88

99
import_and_load_builtins()
1010

11-
# Set up a Mathics session with definitions.
11+
# Set up two Mathics session with definitions, one for the vectorized routines and
12+
# other for the standard.
1213
# For consistency set the character encoding ASCII which is
1314
# the lowest common denominator available on all systems.
14-
session = MathicsSession(character_encoding="ASCII")
1515

16+
SESSIONS = {
17+
# test.helper session is going to be set up with the library.
18+
True: MathicsSession(character_encoding="ASCII"),
19+
# Default non-vectorized
20+
False: MathicsSession(character_encoding="ASCII"),
21+
}
1622

17-
def reset_session(add_builtin=True, catch_interrupt=False):
18-
global session
19-
session.reset()
2023

21-
22-
def evaluate_value(str_expr: str):
23-
expr = session.evaluate(str_expr)
24+
def expr_to_value(expr: BaseElement):
2425
if isinstance(expr, Symbol):
2526
return expr.name
2627
return expr.value
2728

2829

29-
def evaluate(str_expr: str):
30-
return session.evaluate(str_expr)
31-
32-
3330
def check_evaluation(
3431
str_expr: str,
3532
str_expected: str,
@@ -39,6 +36,7 @@ def check_evaluation(
3936
to_string_expected: bool = True,
4037
to_python_expected: bool = False,
4138
expected_messages: Optional[tuple] = None,
39+
use_vectorized: bool = True,
4240
):
4341
"""
4442
Helper function to test Mathics expression against
@@ -66,34 +64,41 @@ def check_evaluation(
6664
expected_messages ``Optional[tuple[str]]``: If a tuple of strings are passed into this parameter, messages and prints raised during
6765
the evaluation of ``str_expr`` are compared with the elements of the list. If ``None``, this comparison
6866
is ommited.
67+
68+
use_vectorized: bool
69+
If True, use the session with `pymathics.vectorizedplot` loaded.
6970
"""
71+
current_session = SESSIONS[use_vectorized]
72+
7073
if str_expr is None:
71-
reset_session()
72-
evaluate('LoadModule["pymathics.vectorizedplot"]')
74+
current_session.reset()
75+
current_session.evaluate('LoadModule["pymathics.vectorizedplot"]')
7376
return
7477

7578
if to_string_expr:
7679
str_expr = f"ToString[{str_expr}]"
77-
result = evaluate_value(str_expr)
80+
result = expr_to_value(current_session.evaluate(str_expr))
7881
else:
79-
result = evaluate(str_expr)
82+
result = current_session.evaluate(str_expr)
8083

81-
outs = [out.text for out in session.evaluation.out]
84+
outs = [out.text for out in current_session.evaluation.out]
8285

8386
if to_string_expected:
8487
if hold_expected:
8588
expected = str_expected
8689
else:
8790
str_expected = f"ToString[{str_expected}]"
88-
expected = evaluate_value(str_expected)
91+
expected = expr_to_value(current_session.evaluate(str_expected))
8992
else:
9093
if hold_expected:
9194
if to_python_expected:
9295
expected = str_expected
9396
else:
94-
expected = evaluate(f"HoldForm[{str_expected}]").elements[0]
97+
expected = current_session.evaluate(
98+
f"HoldForm[{str_expected}]"
99+
).elements[0]
95100
else:
96-
expected = evaluate(str_expected)
101+
expected = current_session.evaluate(str_expected)
97102
if to_python_expected:
98103
expected = expected.to_python(string_quotes=False)
99104

test/test_plot.py

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

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

88
import pytest
99
from mathics.core.expression import Expression
@@ -37,6 +37,7 @@ def test__listplot():
3737
hold_expected=True,
3838
failure_message=fail_msg,
3939
expected_messages=msgs,
40+
use_vectorized=False,
4041
)
4142

4243

@@ -248,6 +249,7 @@ def test_plot(str_expr, msgs, str_expected, fail_msg):
248249
hold_expected=True,
249250
failure_message=fail_msg,
250251
expected_messages=msgs,
252+
use_vectorized=False,
251253
)
252254

253255

@@ -302,6 +304,8 @@ def mark(parent_expr, marker):
302304

303305

304306
def eval_and_check_structure(str_expr, str_expected):
307+
session = SESSIONS[False]
308+
session.reset()
305309
expr = session.parse(str_expr)
306310
result = expr.evaluate(session.evaluation)
307311
expected = session.parse(str_expected)

0 commit comments

Comments
 (0)