Skip to content

Commit bd58f4e

Browse files
committed
Merge remote-tracking branch 'origin/evaluate_without_side_effects' into evaluate_without_side_effects
2 parents d4696d0 + 8e8d5cd commit bd58f4e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+48349
-126
lines changed

SYMBOLS_MANIFEST.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ System`Context
265265
System`Contexts
266266
System`Continue
267267
System`ContinuedFraction
268+
System`ContourPlot
268269
System`Convert`B64Dump`B64Decode
269270
System`Convert`B64Dump`B64Encode
270271
System`ConvertersDump`$ExtensionMappings

mathics/builtin/arithmetic.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -802,8 +802,13 @@ def to_sympy(self, expr, **kwargs):
802802
try:
803803
e_kwargs = kwargs.copy()
804804
e_kwargs["convert_all_global_functions"] = True
805+
e_kwargs["dummies"] = e_kwargs.get("dummies", set()).union((index,))
805806
e = expr.elements[0].to_sympy(**e_kwargs)
806-
i = index.elements[0].to_sympy(**kwargs)
807+
e_kwargs["convert_all_global_functions"] = kwargs.get(
808+
"convert_all_global_functions", False
809+
)
810+
811+
i = index.elements[0].to_sympy(**e_kwargs)
807812
start = index.elements[1].to_sympy(**kwargs)
808813
stop = index.elements[2].to_sympy(**kwargs)
809814

@@ -1045,23 +1050,27 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
10451050
index = expr.elements[1]
10461051
arg_kwargs = kwargs.copy()
10471052
arg_kwargs["convert_all_global_functions"] = True
1053+
arg_kwargs["dummies"] = kwargs.get("dummies", set()).union((index,))
10481054
f_sympy = expr.elements[0].to_sympy(**arg_kwargs)
10491055
if f_sympy is None:
10501056
return
10511057

10521058
evaluation = kwargs.get("evaluation", None)
10531059

10541060
# Handle summation parameters: variable, min, max
1055-
var_min_max = index.elements[:3]
1056-
bounds = [expr.to_sympy(**kwargs) for expr in var_min_max]
10571061

1062+
arg_kwargs["convert_all_global_functions"] = kwargs.get(
1063+
"convert_all_global_functions", False
1064+
)
1065+
var_min_max = index.elements[:3]
1066+
bounds = [expr.to_sympy(**arg_kwargs) for expr in var_min_max]
10581067
if evaluation:
10591068
# Min and max might be Mathics expressions. If so, evaluate them.
10601069
for i in (1, 2):
10611070
min_max_expr = var_min_max[i]
10621071
if not isinstance(expr, Symbol):
10631072
min_max_expr_eval = min_max_expr.evaluate(evaluation)
1064-
value = min_max_expr_eval.to_sympy(**kwargs)
1073+
value = min_max_expr_eval.to_sympy(**arg_kwargs)
10651074
bounds[i] = value
10661075

10671076
# FIXME: The below tests on SympyExpression, but really the
@@ -1075,7 +1084,7 @@ def to_sympy(self, expr, **kwargs) -> Optional[SympyExpression]:
10751084
# If we have integer bounds, we'll use Mathics's iterator Sum
10761085
# (which is Plus)
10771086

1078-
if all(
1087+
if evaluation and all(
10791088
(hasattr(i, "is_integer") and i.is_integer)
10801089
or (hasattr(i, "is_finite") and i.is_finite and i.is_constant())
10811090
for i in bounds[1:]

mathics/builtin/box/graphics3d.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,17 @@ def _prepare_elements(self, elements, options, max_width=None):
250250
self.background_color = elements.background_color
251251

252252
def calc_dimensions(final_pass=True):
253+
# TODO: the code below is broken in any other case but Automatic
254+
# because it calls elements.translate which is not implemented.
255+
# Plots may pass specific plot ranges, triggering this deficiency
256+
# and causing tests to fail The following line avoids this,
257+
# and it should not change the behavior of any case which did
258+
# previously fail with an exception.
259+
#
260+
# This code should be DRYed (together with the very similar code
261+
# for the 2d case), and the missing .translate method added.
262+
plot_range = ["System`Automatic"] * 3
263+
253264
if "System`Automatic" in plot_range:
254265
xmin, xmax, ymin, ymax, zmin, zmax = elements.extent()
255266
else:
@@ -291,7 +302,7 @@ def calc_dimensions(final_pass=True):
291302
elif zmin == zmax:
292303
zmin -= 1
293304
zmax += 1
294-
elif isinstance(plot_range[1], list) and len(plot_range[1]) == 2:
305+
elif isinstance(plot_range[1], list) and len(plot_range[2]) == 2:
295306
zmin, zmax = list(map(float, plot_range[2]))
296307
zmin = elements.translate((0, 0, zmin))[2]
297308
zmax = elements.translate((0, 0, zmax))[2]

mathics/builtin/drawing/plot.py

Lines changed: 97 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
import palettable
1717

18+
import mathics.eval.drawing.plot3d
19+
import mathics.eval.drawing.plot3d_vectorized
1820
from mathics.builtin.drawing.graphics3d import Graphics3D
1921
from mathics.builtin.graphics import Graphics
2022
from mathics.builtin.options import options_to_rules
@@ -37,6 +39,7 @@
3739
SymbolLine,
3840
SymbolLog10,
3941
SymbolNone,
42+
SymbolPlotRange,
4043
SymbolRGBColor,
4144
SymbolSequence,
4245
SymbolStyle,
@@ -62,21 +65,25 @@
6265
# Set option such as $UseVectorizedPlot, and maybe a non-standard Plot3D option.
6366
# For now an env variable is simplest.
6467
# TODO: work out exactly how to deploy.
68+
69+
70+
# can be set via environment variable at startup time,
71+
# or changed dynamically by setting the use_vectorized_plot flag
6572
use_vectorized_plot = os.getenv("MATHICS3_USE_VECTORIZED_PLOT", False)
66-
if use_vectorized_plot:
67-
from mathics.eval.drawing.plot3d_vectorized import (
68-
eval_ComplexPlot,
69-
eval_ComplexPlot3D,
70-
eval_DensityPlot,
71-
eval_Plot3D,
72-
)
73-
else:
74-
from mathics.eval.drawing.plot3d import (
75-
eval_ComplexPlot,
76-
eval_ComplexPlot3D,
77-
eval_DensityPlot,
78-
eval_Plot3D,
73+
74+
75+
# get the plot eval function for the given class,
76+
# depending on whether vectorized plot functions are enabled
77+
def get_plot_eval_function(cls):
78+
function_name = "eval_" + cls.__name__
79+
plot_module = (
80+
mathics.eval.drawing.plot3d_vectorized
81+
if use_vectorized_plot
82+
else mathics.eval.drawing.plot3d
7983
)
84+
fun = getattr(plot_module, function_name)
85+
return fun
86+
8087

8188
# This tells documentation how to sort this module
8289
# Here we are also hiding "drawing" since this erroneously appears at the top level.
@@ -467,7 +474,7 @@ def error(self, what, *args, **kwargs):
467474
def __init__(self, expr, range_exprs, options, evaluation):
468475
self.evaluation = evaluation
469476

470-
# plot ranges
477+
# plot ranges of the form {x,xmin,xmax} etc.
471478
self.ranges = []
472479
for range_expr in range_exprs:
473480
if not range_expr.has_form("List", 3):
@@ -488,6 +495,19 @@ def __init__(self, expr, range_exprs, options, evaluation):
488495
self.error(expr, "invrange", range_expr)
489496
self.ranges.append(range)
490497

498+
# Contours option
499+
contours = expr.get_option(options, "Contours", evaluation)
500+
if contours is not None:
501+
c = contours.to_python()
502+
if not (
503+
c == "System`Automatic"
504+
or isinstance(c, int)
505+
or isinstance(c, tuple)
506+
and all(isinstance(cc, (int, float)) for cc in c)
507+
):
508+
self.error(expr, "invcontour", contours)
509+
self.contours = c
510+
491511
# Mesh option
492512
mesh = expr.get_option(options, "Mesh", evaluation)
493513
if mesh not in (SymbolNone, SymbolFull, SymbolAll):
@@ -579,6 +599,9 @@ class _Plot3D(Builtin):
579599
"Plot range `1` must be of the form {variable, min, max}, "
580600
"where max > min."
581601
),
602+
"invcontour": (
603+
"Contours option must be Automatic, an integer, or a list of numbers."
604+
),
582605
}
583606

584607
# Plot3D, ComplexPlot3D
@@ -630,9 +653,43 @@ def eval(
630653
plot_options.functions = [functions]
631654

632655
# subclass must set eval_function and graphics_class
633-
graphics = self.eval_function(plot_options, evaluation)
656+
eval_function = get_plot_eval_function(self.__class__)
657+
graphics = eval_function(plot_options, evaluation)
634658
if not graphics:
635659
return
660+
661+
# Expand PlotRange option using the {x,xmin,xmax} etc. range specifications
662+
# Pythonize it, so Symbol becomes str, numeric becomes int or float
663+
plot_range = self.get_option(options, str(SymbolPlotRange), evaluation)
664+
plot_range = plot_range.to_python()
665+
dim = 3 if self.graphics_class is Graphics3D else 2
666+
if isinstance(plot_range, str):
667+
# PlotRange -> Automatic becomes PlotRange -> {Automatic, ...}
668+
plot_range = [str(SymbolAutomatic)] * dim
669+
if isinstance(plot_range, (int, float)):
670+
# PlotRange -> s becomes PlotRange -> {Automatic,...,{-s,s}}
671+
pr = plot_range
672+
plot_range = [str(SymbolAutomatic)] * dim
673+
plot_range[-1] = [-pr, pr]
674+
elif isinstance(plot_range, (list, tuple)) and isinstance(
675+
plot_range[0], (int, float)
676+
):
677+
# PlotRange -> {s0,s1} becomes PlotRange -> {Automatic,...,{s0,s1}}
678+
pr = plot_range
679+
plot_range = [str(SymbolAutomatic)] * dim
680+
plot_range[-1] = pr
681+
682+
# now we have a list of length dim
683+
# handle Automatic ~ {xmin,xmax} etc.
684+
for i, (pr, r) in enumerate(zip(plot_range, plot_options.ranges)):
685+
# TODO: this treats Automatic and Full as the same, which isn't quite right
686+
if isinstance(pr, str) and not isinstance(r[1], complex):
687+
plot_range[i] = r[1:] # extract {xmin,xmax} from {x,xmin,xmax}
688+
689+
# unpythonize and update PlotRange option
690+
options[str(SymbolPlotRange)] = to_mathics_list(*plot_range)
691+
692+
# generate the Graphics[3D] result
636693
graphics_expr = graphics.generate(
637694
options_to_rules(options, self.graphics_class.options)
638695
)
@@ -781,7 +838,6 @@ class ComplexPlot3D(_Plot3D):
781838
options = _Plot3D.options3d | {"Mesh": "None"}
782839

783840
many_functions = True
784-
eval_function = staticmethod(eval_ComplexPlot3D)
785841
graphics_class = Graphics3D
786842

787843

@@ -805,7 +861,31 @@ class ComplexPlot(_Plot3D):
805861
options = _Plot3D.options2d
806862

807863
many_functions = False
808-
eval_function = staticmethod(eval_ComplexPlot)
864+
graphics_class = Graphics
865+
866+
867+
class ContourPlot(_Plot3D):
868+
"""
869+
<url>:WMA link: https://reference.wolfram.com/language/ref/ContourPlot.html</url>
870+
<dl>
871+
<dt>'Contour'[$f$, {$x$, $x_{min}$, $x_{max}$}, {$y$, $y_{min}$, $y_{max}$}]
872+
<dd>creates a two-dimensional contour plot ofh $f$ over the region
873+
$x$ ranging from $x_{min}$ to $x_{max}$ and $y$ ranging from $y_{min}$ to $y_{max}$.
874+
875+
See <url>:Drawing Option and Option Values:
876+
/doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values
877+
</url> for a list of Plot options.
878+
</dl>
879+
880+
"""
881+
882+
requires = ["skimage"]
883+
summary_text = "creates a contour plot"
884+
expected_args = 3
885+
options = _Plot3D.options2d | {"Contours": "Automatic"}
886+
# TODO: other options?
887+
888+
many_functions = True
809889
graphics_class = Graphics
810890

811891

@@ -838,7 +918,6 @@ class DensityPlot(_Plot3D):
838918
options = _Plot3D.options2d
839919

840920
many_functions = False
841-
eval_function = staticmethod(eval_DensityPlot)
842921
graphics_class = Graphics
843922

844923

@@ -1958,5 +2037,4 @@ class Plot3D(_Plot3D):
19582037
options = _Plot3D.options3d
19592038

19602039
many_functions = True
1961-
eval_function = staticmethod(eval_Plot3D)
19622040
graphics_class = Graphics3D

mathics/builtin/numbers/calculus.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
SymbolPower,
5858
SymbolTimes,
5959
SymbolTrue,
60+
sympy_name,
6061
)
6162
from mathics.core.systemsymbols import (
6263
SymbolAnd,
@@ -528,7 +529,7 @@ def to_sympy(self, expr, **kwargs):
528529
return
529530

530531
func = exprs[1].elements[0]
531-
sym_func = sympy.Function(str(SYMPY_SYMBOL_PREFIX + func.__str__()))(*sym_args)
532+
sym_func = sympy.Function(sympy_name(func))(*sym_args)
532533

533534
counts = [element.get_int_value() for element in exprs[2].elements]
534535
if None in counts:

mathics/core/builtin.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,12 +864,14 @@ class UnavailableFunction:
864864

865865
def __init__(self, builtin):
866866
self.name = builtin.get_name()
867+
self.requires = builtin.requires
867868

868869
def __call__(self, **kwargs):
869870
kwargs["evaluation"].message(
870871
"General",
871872
"pyimport", # see messages.py for error message definition
872873
strip_context(self.name),
874+
", ".join(self.requires),
873875
)
874876

875877

0 commit comments

Comments
 (0)