Skip to content

Commit 9481d26

Browse files
authored
1 parent 6a09aab commit 9481d26

File tree

4 files changed

+255
-119
lines changed

4 files changed

+255
-119
lines changed

SYMBOLS_MANIFEST.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ System`Complement
244244
System`Complex
245245
System`ComplexExpand
246246
System`ComplexInfinity
247+
System`ComplexPlot
248+
System`ComplexPlot3D
247249
System`Complexes
248250
System`CompositeQ
249251
System`Composition

mathics/builtin/drawing/plot.py

Lines changed: 123 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
SymbolLog10,
3939
SymbolNone,
4040
SymbolRGBColor,
41+
SymbolSequence,
4142
SymbolStyle,
4243
)
4344
from mathics.eval.drawing.charts import draw_bar_chart, eval_chart
@@ -53,6 +54,7 @@
5354
get_plot_range,
5455
get_plot_range_option,
5556
)
57+
from mathics.eval.nevaluator import eval_N
5658

5759
# The vectorized plot function generates GraphicsComplex using NumericArray,
5860
# which no consumer will currently understand. So lets make it opt-in for now.
@@ -62,11 +64,19 @@
6264
# TODO: work out exactly how to deploy.
6365
use_vectorized_plot = os.getenv("MATHICS3_USE_VECTORIZED_PLOT", False)
6466
if use_vectorized_plot:
65-
from mathics.eval.drawing.plot3d_vectorized import eval_DensityPlot, eval_Plot3D
67+
from mathics.eval.drawing.plot3d_vectorized import (
68+
eval_ComplexPlot,
69+
eval_ComplexPlot3D,
70+
eval_DensityPlot,
71+
eval_Plot3D,
72+
)
6673
else:
67-
from mathics.eval.drawing.plot3d import eval_DensityPlot, eval_Plot3D
68-
69-
from mathics.eval.nevaluator import eval_N
74+
from mathics.eval.drawing.plot3d import (
75+
eval_ComplexPlot,
76+
eval_ComplexPlot3D,
77+
eval_DensityPlot,
78+
eval_Plot3D,
79+
)
7080

7181
# This tells documentation how to sort this module
7282
# Here we are also hiding "drawing" since this erroneously appears at the top level.
@@ -466,11 +476,15 @@ def __init__(self, expr, range_exprs, options, evaluation):
466476
self.error(expr, "invrange", range_expr)
467477
range = [range_expr.elements[0]]
468478
for limit_expr in range_expr.elements[1:3]:
469-
limit = limit_expr.round_to_float(evaluation)
470-
if limit is None:
479+
limit = eval_N(limit_expr, evaluation).to_python()
480+
if not isinstance(limit, (int, float, complex)):
471481
self.error(expr, "plln", limit_expr, range_expr)
472482
range.append(limit)
473-
if range[2] <= range[1]:
483+
if isinstance(limit, (int, float)) and range[2] <= range[1]:
484+
self.error(expr, "invrange", range_expr)
485+
if isinstance(limit, complex) and (
486+
range[2].real <= range[1].real or range[2].imag <= range[1].imag
487+
):
474488
self.error(expr, "invrange", range_expr)
475489
self.ranges.append(range)
476490

@@ -539,7 +553,9 @@ def check_plotpoints(steps):
539553

540554

541555
class _Plot3D(Builtin):
542-
"""Common base class for Plot3D and DensityPlot"""
556+
"""Common base class for Plot3D, DensityPlot, ComplexPlot, ComplexPlot3D"""
557+
558+
attributes = A_HOLD_ALL | A_PROTECTED
543559

544560
# Check for correct number of args
545561
eval_error = Builtin.generic_argument_error
@@ -565,29 +581,62 @@ class _Plot3D(Builtin):
565581
),
566582
}
567583

584+
# Plot3D, ComplexPlot3D
585+
options3d = Graphics3D.options | {
586+
"Axes": "True",
587+
"AspectRatio": "1",
588+
"Mesh": "Full",
589+
"PlotPoints": "None",
590+
"BoxRatios": "{1, 1, 0.4}",
591+
"MaxRecursion": "2",
592+
}
593+
594+
# DensityPlot, ComplexPlot
595+
options2d = Graphics.options | {
596+
"Axes": "False",
597+
"AspectRatio": "1",
598+
"Mesh": "None",
599+
"Frame": "True",
600+
"ColorFunction": "Automatic",
601+
"ColorFunctionScaling": "True",
602+
"PlotPoints": "None",
603+
"MaxRecursion": "0",
604+
# 'MaxRecursion': '2', # FIXME causes bugs in svg output see #303
605+
}
606+
568607
def eval(
569608
self,
570609
functions,
571-
xrange,
572-
yrange,
610+
ranges,
573611
evaluation: Evaluation,
574612
options: dict,
575613
):
576-
"""%(name)s[functions_, xrange_, yrange_, OptionsPattern[%(name)s]]"""
614+
"""%(name)s[functions_, ranges__, OptionsPattern[%(name)s]]"""
577615

578616
# TODO: test error for too many, too few, no args
579617

580618
# parse options, bailing out if anything is wrong
581619
try:
582-
plot_options = PlotOptions(self, [xrange, yrange], options, evaluation)
620+
ranges = ranges.elements if ranges.head is SymbolSequence else [ranges]
621+
plot_options = PlotOptions(self, ranges, options, evaluation)
583622
except ValueError:
584623
return None
585624

586-
# ask the subclass to get one or more functions as appropriate
587-
plot_options.functions = self.get_functions_param(functions)
625+
# TODO: consult many_functions variable set by subclass and error
626+
# if many_functions is False but multiple are supplied
627+
if functions.has_form("List", None):
628+
plot_options.functions = functions.elements
629+
else:
630+
plot_options.functions = [functions]
588631

589-
# delegate to subclass, which will call the appropriate eval_* function
590-
return self.do_eval(plot_options, evaluation, options)
632+
# subclass must set eval_function and graphics_class
633+
graphics = self.eval_function(plot_options, evaluation)
634+
if not graphics:
635+
return
636+
graphics_expr = graphics.generate(
637+
options_to_rules(options, self.graphics_class.options)
638+
)
639+
return graphics_expr
591640

592641

593642
class BarChart(_Chart):
@@ -712,6 +761,54 @@ class ColorDataFunction(Builtin):
712761
summary_text = "color scheme object"
713762

714763

764+
class ComplexPlot3D(_Plot3D):
765+
"""
766+
<url>:WMA link: https://reference.wolfram.com/language/ref/ComplexPlot3D.html</url>
767+
<dl>
768+
<dt>'Plot3D'[$f$, {$z$, $z_{min}$, $z_{max}$}]
769+
<dd>creates a three-dimensional plot of the magnitude of $f$ with $z$ ranging from $z_{min}$ to \
770+
$z_{max}$ with surface colored according to phase
771+
772+
See <url>:Drawing Option and Option Values:
773+
/doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values
774+
</url> for a list of Plot options.
775+
</dl>
776+
777+
"""
778+
779+
summary_text = "plots one or more complex functions as a surface"
780+
expected_args = 2
781+
options = _Plot3D.options3d | {"Mesh": "None"}
782+
783+
many_functions = True
784+
eval_function = staticmethod(eval_ComplexPlot3D)
785+
graphics_class = Graphics3D
786+
787+
788+
class ComplexPlot(_Plot3D):
789+
"""
790+
<url>:WMA link: https://reference.wolfram.com/language/ref/ComplexPlot.html</url>
791+
<dl>
792+
<dt>'Plot3D'[$f$, {$z$, $z_{min}$, $z_{max}$}]
793+
<dd>creates two-dimensional plot of $f$ with $z$ ranging from $z_{min}$ to \
794+
$z_{max}$ colored according to phase
795+
796+
See <url>:Drawing Option and Option Values:
797+
/doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values
798+
</url> for a list of Plot options.
799+
</dl>
800+
801+
"""
802+
803+
summary_text = "plots a complex function showing phase using colors"
804+
expected_args = 2
805+
options = _Plot3D.options2d
806+
807+
many_functions = False
808+
eval_function = staticmethod(eval_ComplexPlot)
809+
graphics_class = Graphics
810+
811+
715812
class DensityPlot(_Plot3D):
716813
"""
717814
<url>:WMA link: https://reference.wolfram.com/language/ref/DensityPlot.html</url>
@@ -736,35 +833,13 @@ class DensityPlot(_Plot3D):
736833
= -Graphics-
737834
"""
738835

739-
attributes = A_HOLD_ALL | A_PROTECTED
740-
741-
options = Graphics.options.copy()
742-
options.update(
743-
{
744-
"Axes": "False",
745-
"AspectRatio": "1",
746-
"Mesh": "None",
747-
"Frame": "True",
748-
"ColorFunction": "Automatic",
749-
"ColorFunctionScaling": "True",
750-
"PlotPoints": "None",
751-
"MaxRecursion": "0",
752-
# 'MaxRecursion': '2', # FIXME causes bugs in svg output see #303
753-
}
754-
)
755836
summary_text = "density plot for a function"
837+
expected_args = 3
838+
options = _Plot3D.options2d
756839

757-
# TODO: error if more than one function here
758-
def get_functions_param(self, functions):
759-
"""can only have one function"""
760-
return [functions]
761-
762-
# called by superclass
763-
def do_eval(self, plot_options, evaluation, options):
764-
"""called by superclass to call appropriate eval_* function"""
765-
graphics = eval_DensityPlot(plot_options, evaluation)
766-
graphics_expr = graphics.generate(options_to_rules(options, Graphics.options))
767-
return graphics_expr
840+
many_functions = False
841+
eval_function = staticmethod(eval_DensityPlot)
842+
graphics_class = Graphics
768843

769844

770845
class DiscretePlot(_Plot):
@@ -1878,30 +1953,10 @@ class Plot3D(_Plot3D):
18781953
= -Graphics3D-
18791954
"""
18801955

1881-
attributes = A_HOLD_ALL | A_PROTECTED
1882-
1883-
options = Graphics.options.copy()
1884-
options.update(
1885-
{
1886-
"Axes": "True",
1887-
"AspectRatio": "1",
1888-
"Mesh": "Full",
1889-
"PlotPoints": "None",
1890-
"BoxRatios": "{1, 1, 0.4}",
1891-
"MaxRecursion": "2",
1892-
}
1893-
)
18941956
summary_text = "plots 3D surfaces of one or more functions"
1957+
expected_args = 3
1958+
options = _Plot3D.options3d
18951959

1896-
def get_functions_param(self, functions):
1897-
"""May have a function or a list of functions"""
1898-
if functions.has_form("List", None):
1899-
return functions.elements
1900-
else:
1901-
return [functions]
1902-
1903-
def do_eval(self, plot_options, evaluation, options):
1904-
"""called by superclass to call appropriate eval_* function"""
1905-
graphics = eval_Plot3D(plot_options, evaluation)
1906-
graphics_expr = graphics.generate(options_to_rules(options, Graphics3D.options))
1907-
return graphics_expr
1960+
many_functions = True
1961+
eval_function = staticmethod(eval_Plot3D)
1962+
graphics_class = Graphics3D

mathics/eval/drawing/plot3d.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,3 +492,17 @@ def eval_color(x, y, v):
492492
graphics.add_linexyzs([mesh_points[xi]])
493493

494494
return graphics
495+
496+
497+
def eval_ComplexPlot3D(
498+
plot_options,
499+
evaluation: Evaluation,
500+
):
501+
return None
502+
503+
504+
def eval_ComplexPlot(
505+
plot_options,
506+
evaluation: Evaluation,
507+
):
508+
return None

0 commit comments

Comments
 (0)