diff --git a/mathics/builtin/drawing/plot.py b/mathics/builtin/drawing/plot.py index b2d7fee3f..8c5986fd3 100644 --- a/mathics/builtin/drawing/plot.py +++ b/mathics/builtin/drawing/plot.py @@ -40,6 +40,7 @@ from mathics.eval.drawing.charts import draw_bar_chart, eval_chart from mathics.eval.drawing.colors import COLOR_PALETTES, get_color_palette from mathics.eval.drawing.plot import ( + ListPlotPairOfNumbersError, ListPlotType, check_plot_range, compile_quiet_function, @@ -96,11 +97,12 @@ class _ListPlot(Builtin, ABC): attributes = A_PROTECTED | A_READ_PROTECTED messages = { + "joind": "Value of option Joined -> `1` is not True or False.", + "lpn": "`1` is not a list of numbers or pairs of numbers.", "prng": ( "Value of option PlotRange -> `1` is not All, Automatic or " "an appropriate list of range specifications." ), - "joind": "Value of option Joined -> `1` is not True or False.", } use_log_scale = False @@ -110,8 +112,7 @@ def eval(self, points, evaluation: Evaluation, options: dict): class_name = self.__class__.__name__ - # Scale point values down by Log 10. Tick mark values will be adjusted - # to be 10^n in GraphicsBox. + # Scale point values down by Log 10. Tick mark values will be adjusted to be 10^n in GraphicsBox. if self.use_log_scale: points = ListExpression( *( @@ -120,6 +121,21 @@ def eval(self, points, evaluation: Evaluation, options: dict): ) ) + points = points.evaluate(evaluation) + if not isinstance(points, ListExpression): + evaluation.message(class_name, "lpn", points) + return + + if not all( + element.is_numeric(evaluation) + or isinstance(element, ListExpression) + or (1 <= len(element.elements) <= 2) + or (len(element.elements) == 1 and isinstance(element[0], ListExpression)) + for element in points.elements + ): + evaluation.message(class_name, "lpn", points) + return + # If "points" is a literal value with a Python representation, # it has a ".value" attribute with a non-None value. So here, # we don't have to eval_N().to_python(). @@ -152,17 +168,21 @@ def eval(self, points, evaluation: Evaluation, options: dict): evaluation.message(class_name, "joind", joined_option, expr) is_joined_plot = False - return eval_ListPlot( - all_points, - x_range, - y_range, - is_discrete_plot=False, - is_joined_plot=is_joined_plot, - filling=filling, - use_log_scale=self.use_log_scale, - list_plot_type=plot_type, - options=options, - ) + try: + return eval_ListPlot( + all_points, + x_range, + y_range, + is_discrete_plot=False, + is_joined_plot=is_joined_plot, + filling=filling, + use_log_scale=self.use_log_scale, + list_plot_type=plot_type, + options=options, + ) + except ListPlotPairOfNumbersError: + evaluation.message(class_name, "lpn", points) + return class _Plot(Builtin, ABC): diff --git a/mathics/builtin/specialfns/hypergeom.py b/mathics/builtin/specialfns/hypergeom.py index 397ff4792..61cb92fed 100644 --- a/mathics/builtin/specialfns/hypergeom.py +++ b/mathics/builtin/specialfns/hypergeom.py @@ -224,7 +224,7 @@ class HypergeometricU(MPMathFunction): Result is symbollicaly simplified, where possible: >> HypergeometricU[3, 2, 1] - = MeijerG[{{-2}, {}}, {{0, -1}, {}}, 1] / 2 + = MeijerG[{{-2}, {}}, ... >> HypergeometricU[1,4,8] = HypergeometricU[1, 4, 8] unless a numerical evaluation is explicitly requested: diff --git a/mathics/core/expression.py b/mathics/core/expression.py index 74dd75c2b..4c681a78a 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -1365,8 +1365,13 @@ def rules(): if not isinstance(result, EvalMixin): return result, False if result.sameQ(new): - new._timestamp_cache(evaluation) - return new, False + # Even though result and new may be the same, + # new can be a Expression[SymbolConstant: System`List...] + # while "result' might be ListExpression!? + # So make sure to use "result", not "new". + if isinstance(result, Expression): + result._timestamp_cache(evaluation) + return result, False else: return result, True diff --git a/mathics/eval/drawing/plot.py b/mathics/eval/drawing/plot.py index c814f871a..38abec81b 100644 --- a/mathics/eval/drawing/plot.py +++ b/mathics/eval/drawing/plot.py @@ -54,6 +54,14 @@ has_compile = False +class ListPlotPairOfNumbersError(Exception): + """ + Called eval_ListPlot with a plot group that is not a list of pairs. + """ + + pass + + def automatic_plot_range(values): """Calculates mean and standard deviation, throwing away all points which are more than 'thresh' number of standard deviations away from @@ -279,6 +287,7 @@ def eval_ListPlot( # He have a list of plot groups if all( isinstance(point, (list, tuple)) and len(point) == 2 + for _ in plot_groups for point in plot_groups ): pass @@ -308,9 +317,9 @@ def eval_ListPlot( i = 0 while i < len(plot_groups[lidx]): seg = plot_group[i] - # skip empty segments How do they get in though? + # If there is an empty segment, that is an error. if not seg: - continue + raise ListPlotPairOfNumbersError for j, point in enumerate(seg): x_min = min(x_min, point[0]) x_max = max(x_min, point[0]) diff --git a/mathics/interrupt.py b/mathics/interrupt.py index 264266581..947c8f82c 100644 --- a/mathics/interrupt.py +++ b/mathics/interrupt.py @@ -105,9 +105,12 @@ def Mathics3_interrupt_handler( if eval_frame is None: continue eval_method_name = eval_frame.f_code.co_name - eval_method = getattr(eval_frame.f_locals.get("self"), eval_method_name) + self_param = eval_frame.f_locals.get("self") + name = self_param.__class__.__name__ + eval_method = getattr(self_param, eval_method_name) if eval_method: - print_fn(eval_method.__doc__) + replaced_doc = eval_method.__doc__.replace("%(name)", name) + print_fn(replaced_doc) eval_expression = get_eval_Expression() if eval_expression is not None: print_fn(str(eval_expression)) diff --git a/test/builtin/drawing/test_plot.py b/test/builtin/drawing/test_plot.py index b57e588c6..ba53e7206 100644 --- a/test/builtin/drawing/test_plot.py +++ b/test/builtin/drawing/test_plot.py @@ -8,6 +8,35 @@ import pytest +def test__listplot(): + """tests for module builtin.drawing.plot._ListPlot""" + for str_expr, msgs, str_expected, fail_msg in ( + ( + "ListPlot[5]", + ("5 is not a list of numbers or pairs of numbers.",), + "ListPlot[5]", + "ListPlot with invalid list of point", + ), + ( + "ListLinePlot[{{}, {{1., 1.}}, {{1., 2.}}, {}}]", + ( + "{{}, {{1., 1.}}, {{1., 2.}}, {}} is not a list of numbers or pairs of numbers.", + ), + "ListLinePlot[{{}, {{1., 1.}}, {{1., 2.}}, {}}]", + "ListLinePlot with invalid list of point", + ), + ): + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) + + @pytest.mark.parametrize( ("str_expr", "msgs", "str_expected", "fail_msg"), [ @@ -161,8 +190,8 @@ ), ], ) -def test_private_doctests_plot(str_expr, msgs, str_expected, fail_msg): - """builtin.drawing.plot""" +def test_plot(str_expr, msgs, str_expected, fail_msg): + """tests for module builtin.drawing.plot""" check_evaluation( str_expr, str_expected, diff --git a/test/builtin/specialfns/test_hypergeom.py b/test/builtin/specialfns/test_hypergeom.py index 8e408fea3..83a076e8a 100644 --- a/test/builtin/specialfns/test_hypergeom.py +++ b/test/builtin/specialfns/test_hypergeom.py @@ -128,12 +128,6 @@ def test_HypergeometricU(str_expr, msgs, str_expected, fail_msg): None, ), ("N[MeijerG[{{},{}},{{0,0},{0,0}},100^2]]", None, "0.000893912", None), - ( - "HypergeometricU[{3,1},{2,4},{7,8}]", - None, - "{MeijerG[{{-2}, {}}, {{0, -1}, {}}, 7] / 2, HypergeometricU[1, 4, 8]}", - None, - ), ], ) def test_MeijerG(str_expr, msgs, str_expected, fail_msg):