Skip to content

Commit 0e8b51e

Browse files
committed
Initial WIP
1 parent 6a09aab commit 0e8b51e

File tree

4 files changed

+133
-74
lines changed

4 files changed

+133
-74
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: 85 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from mathics.core.evaluation import Evaluation
2727
from mathics.core.expression import Expression
2828
from mathics.core.list import ListExpression
29+
from mathics.eval.nevaluator import eval_N
2930
from mathics.core.symbols import Symbol, SymbolList
3031
from mathics.core.systemsymbols import (
3132
SymbolAll,
@@ -38,6 +39,7 @@
3839
SymbolLog10,
3940
SymbolNone,
4041
SymbolRGBColor,
42+
SymbolSequence,
4143
SymbolStyle,
4244
)
4345
from mathics.eval.drawing.charts import draw_bar_chart, eval_chart
@@ -62,11 +64,9 @@
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 eval_DensityPlot, eval_Plot3D, eval_ComplexPlot, eval_ComplexPlot3D
6668
else:
67-
from mathics.eval.drawing.plot3d import eval_DensityPlot, eval_Plot3D
68-
69-
from mathics.eval.nevaluator import eval_N
69+
from mathics.eval.drawing.plot3d import eval_DensityPlot, eval_Plot3D, eval_ComplexPlot, eval_ComplexPlot3D
7070

7171
# This tells documentation how to sort this module
7272
# Here we are also hiding "drawing" since this erroneously appears at the top level.
@@ -137,6 +137,7 @@ def eval(self, points, evaluation: Evaluation, options: dict):
137137
)
138138

139139
points = points.evaluate(evaluation)
140+
print("xxx points", points)
140141
if not isinstance(points, ListExpression):
141142
evaluation.message(class_name, "lpn", points)
142143
return
@@ -460,17 +461,20 @@ def __init__(self, expr, range_exprs, options, evaluation):
460461
# plot ranges
461462
self.ranges = []
462463
for range_expr in range_exprs:
464+
print("xxx range_expr", range_expr)
463465
if not range_expr.has_form("List", 3):
464466
self.error(expr, "invrange", range_expr)
465467
if not isinstance(range_expr.elements[0], Symbol):
466468
self.error(expr, "invrange", range_expr)
467469
range = [range_expr.elements[0]]
468470
for limit_expr in range_expr.elements[1:3]:
469-
limit = limit_expr.round_to_float(evaluation)
470-
if limit is None:
471+
limit = eval_N(limit_expr, evaluation).to_python()
472+
if not isinstance(limit, (int,float,complex)):
471473
self.error(expr, "plln", limit_expr, range_expr)
472474
range.append(limit)
473-
if range[2] <= range[1]:
475+
if isinstance(limit, (int,float)) and range[2] <= range[1]:
476+
self.error(expr, "invrange", range_expr)
477+
if isinstance(limit, complex) and (range[2].real <= range[1].real or range[2].imag <= range[1].imag):
474478
self.error(expr, "invrange", range_expr)
475479
self.ranges.append(range)
476480

@@ -539,7 +543,9 @@ def check_plotpoints(steps):
539543

540544

541545
class _Plot3D(Builtin):
542-
"""Common base class for Plot3D and DensityPlot"""
546+
"""Common base class for Plot3D, DensityPlot, ComplexPlot, ComplexPlot3D"""
547+
548+
attributes = A_HOLD_ALL | A_PROTECTED
543549

544550
# Check for correct number of args
545551
eval_error = Builtin.generic_argument_error
@@ -565,29 +571,61 @@ class _Plot3D(Builtin):
565571
),
566572
}
567573

574+
# Plot3D, ComplexPlot3D
575+
options3d = Graphics3D.options | {
576+
"Axes": "True",
577+
"AspectRatio": "1",
578+
"Mesh": "Full",
579+
"PlotPoints": "None",
580+
"BoxRatios": "{1, 1, 0.4}",
581+
"MaxRecursion": "2",
582+
}
583+
584+
# DensityPlot, ComplexPlot
585+
options2d = Graphics.options | {
586+
"Axes": "False",
587+
"AspectRatio": "1",
588+
"Mesh": "None",
589+
"Frame": "True",
590+
"ColorFunction": "Automatic",
591+
"ColorFunctionScaling": "True",
592+
"PlotPoints": "None",
593+
"MaxRecursion": "0",
594+
# 'MaxRecursion': '2', # FIXME causes bugs in svg output see #303
595+
}
596+
568597
def eval(
569598
self,
570599
functions,
571-
xrange,
572-
yrange,
600+
ranges,
573601
evaluation: Evaluation,
574602
options: dict,
575603
):
576-
"""%(name)s[functions_, xrange_, yrange_, OptionsPattern[%(name)s]]"""
604+
"""%(name)s[functions_, ranges__, OptionsPattern[%(name)s]]"""
577605

578606
# TODO: test error for too many, too few, no args
579607

580608
# parse options, bailing out if anything is wrong
581609
try:
582-
plot_options = PlotOptions(self, [xrange, yrange], options, evaluation)
610+
ranges = ranges.elements if ranges.head is SymbolSequence else [ranges]
611+
plot_options = PlotOptions(self, ranges, options, evaluation)
583612
except ValueError:
584613
return None
585614

586-
# ask the subclass to get one or more functions as appropriate
587-
plot_options.functions = self.get_functions_param(functions)
615+
# TODO: consult many_functions variable set by subclass and error
616+
# if many_functions is False but multiple are supplied
617+
if functions.has_form("List", None):
618+
plot_options.functions = functions.elements
619+
else:
620+
plot_options.functions = [functions]
621+
622+
# subclass must set eval_function and graphics_class
623+
graphics = self.eval_function(plot_options, evaluation)
624+
if not graphics:
625+
return
626+
graphics_expr = graphics.generate(options_to_rules(options, self.graphics_class.options))
627+
return graphics_expr
588628

589-
# delegate to subclass, which will call the appropriate eval_* function
590-
return self.do_eval(plot_options, evaluation, options)
591629

592630

593631
class BarChart(_Chart):
@@ -712,6 +750,27 @@ class ColorDataFunction(Builtin):
712750
summary_text = "color scheme object"
713751

714752

753+
class ComplexPlot3D(_Plot3D):
754+
755+
summary_text = "plots one or more complex functions as a surface"
756+
expected_args = 2
757+
options = _Plot3D.options3d
758+
759+
many_functions = True
760+
eval_function = staticmethod(eval_ComplexPlot3D)
761+
graphics_class = Graphics3D
762+
763+
class ComplexPlot(_Plot3D):
764+
765+
summary_text = "plots a complex function showing amplitude and phase using colors"
766+
expected_args = 2
767+
options = _Plot3D.options2d
768+
769+
many_functions = False
770+
eval_function = staticmethod(eval_ComplexPlot)
771+
graphics_class = Graphics
772+
773+
715774
class DensityPlot(_Plot3D):
716775
"""
717776
<url>:WMA link: https://reference.wolfram.com/language/ref/DensityPlot.html</url>
@@ -736,35 +795,13 @@ class DensityPlot(_Plot3D):
736795
= -Graphics-
737796
"""
738797

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-
)
755798
summary_text = "density plot for a function"
799+
expected_args = 3
800+
options = _Plot3D.options2d
756801

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
802+
many_functions = False
803+
eval_function = staticmethod(eval_DensityPlot)
804+
graphics_class = Graphics
768805

769806

770807
class DiscretePlot(_Plot):
@@ -1878,30 +1915,10 @@ class Plot3D(_Plot3D):
18781915
= -Graphics3D-
18791916
"""
18801917

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-
)
18941918
summary_text = "plots 3D surfaces of one or more functions"
1919+
expected_args = 3
1920+
options = _Plot3D.options3d
18951921

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
1922+
many_functions = True
1923+
eval_function = staticmethod(eval_Plot3D)
1924+
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

mathics/eval/drawing/plot3d_vectorized.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@
1717
from .util import GraphicsGenerator
1818

1919

20-
def make_plot(plot_options, evaluation: Evaluation, dim: int):
20+
def make_plot(plot_options, evaluation: Evaluation, dim: int, is_complex: bool):
2121
graphics = GraphicsGenerator(dim)
2222

2323
# pull out plot options
24-
_, xmin, xmax = plot_options.ranges[0]
25-
_, ymin, ymax = plot_options.ranges[1]
24+
if not is_complex:
25+
_, xmin, xmax = plot_options.ranges[0]
26+
_, ymin, ymax = plot_options.ranges[1]
27+
else:
28+
# will generate xs and ys as for real, then combine to form complex cs
29+
_, cmin, cmax = plot_options.ranges[0]
30+
xmin, xmax = cmin.real, cmax.real
31+
ymin, ymax = cmin.imag, cmax.imag
2632
names = [strip_context(str(range[0])) for range in plot_options.ranges]
2733

2834
# Mesh option
@@ -73,7 +79,11 @@ def compute_over_grid(nx, ny):
7379
for function in compiled_functions:
7480
# compute zs from xs and ys using compiled function
7581
with Timer("compute zs"):
76-
zs = function(**{str(names[0]): xs, str(names[1]): ys})
82+
if not is_complex:
83+
zs = function(**{str(names[0]): xs, str(names[1]): ys})
84+
else:
85+
cs = xs + ys * 1j # TODO: fast enough?
86+
zs = function(**{str(names[0]): cs})
7787

7888
# sometimes expr gets compiled into something that returns a complex
7989
# even though the imaginary part is 0
@@ -155,12 +165,28 @@ def eval_Plot3D(
155165
plot_options,
156166
evaluation: Evaluation,
157167
):
158-
return make_plot(plot_options, evaluation, dim=3)
168+
return make_plot(plot_options, evaluation, dim=3, is_complex=False)
159169

160170

161171
@Timer("eval_DensityPlot")
162172
def eval_DensityPlot(
163173
plot_options,
164174
evaluation: Evaluation,
165175
):
166-
return make_plot(plot_options, evaluation, dim=2)
176+
return make_plot(plot_options, evaluation, dim=2, is_complex=False)
177+
178+
179+
@Timer("eval_ComplexPlot3D")
180+
def eval_ComplexPlot3D(
181+
plot_options,
182+
evaluation: Evaluation,
183+
):
184+
return None
185+
186+
187+
@Timer("eval_ComplexPlot")
188+
def eval_ComplexPlot(
189+
plot_options,
190+
evaluation: Evaluation,
191+
):
192+
return make_plot(plot_options, evaluation, dim=2, is_complex=True)

0 commit comments

Comments
 (0)