Skip to content

Commit 5c53483

Browse files
committed
Add support for Mesh
1 parent 09d49a7 commit 5c53483

File tree

2 files changed

+88
-47
lines changed

2 files changed

+88
-47
lines changed

mathics/eval/drawing/plot3d_vectorized.py

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from mathics.core.evaluation import Evaluation
1212
from mathics.core.symbols import strip_context
13-
from mathics.core.systemsymbols import SymbolRGBColor, SymbolNone
13+
from mathics.core.systemsymbols import SymbolRGBColor, SymbolNone, SymbolEdgeForm
1414
from mathics.timing import Timer
1515

1616
from .plot_compile import plot_compile
@@ -27,13 +27,14 @@ def eval_Plot3D(
2727
# pull out plot options
2828
_, xmin, xmax = plot_options.ranges[0]
2929
_, ymin, ymax = plot_options.ranges[1]
30-
nx, ny = plot_options.plotpoints
3130
names = [strip_context(str(range[0])) for range in plot_options.ranges]
3231

33-
# compute (nx, ny) grids of xs and ys for corresponding vertexes
34-
xs = np.linspace(xmin, xmax, nx)
35-
ys = np.linspace(ymin, ymax, ny)
36-
xs, ys = np.meshgrid(xs, ys)
32+
# Mesh option
33+
nmesh = None
34+
if isinstance(plot_options.mesh, int):
35+
nmesh = plot_options.mesh
36+
elif plot_options.mesh is not SymbolNone:
37+
nmesh = 20
3738

3839
# https://davidmathlogic.com/colorblind
3940
palette = [
@@ -48,53 +49,81 @@ def eval_Plot3D(
4849
#(0, 0, 0), # black
4950
]
5051

51-
for i, function in enumerate(plot_options.functions):
52-
with Timer("compile"):
53-
function = plot_compile(evaluation, function, names)
52+
# compile the functions
53+
with Timer("compile"):
54+
compiled_functions = [plot_compile(evaluation, function, names) for function in plot_options.functions]
5455

55-
# compute zs from xs and ys using compiled function
56-
with Timer("compute zs"):
57-
zs = function(**{str(names[0]): xs, str(names[1]): ys})
56+
def compute_over_grid(nx, ny):
5857

59-
# sometimes expr gets compiled into something that returns a complex
60-
# even though the imaginary part is 0
61-
# TODO: check that imag is all 0?
62-
# TODO: needed this for Hypergeometric - look into that
63-
# assert np.all(np.isreal(zs)), "array contains complex values"
64-
zs = np.real(zs)
58+
# compute (nx, ny) grids of xs and ys for corresponding vertexes
59+
xs = np.linspace(xmin, xmax, nx)
60+
ys = np.linspace(ymin, ymax, ny)
61+
xs, ys = np.meshgrid(xs, ys)
6562

66-
# if it's a constant, make it a full array
67-
if isinstance(zs, (float, int, complex)):
68-
zs = np.full(xs.shape, zs)
63+
# (nx,ny) array of numbers from 0 to n-1 that are
64+
# indexes into xyzs array for corresponding vertex
65+
# +1 because these will be used as WL indexes, which are 1-based
66+
inxs = np.arange(math.prod(xs.shape)).reshape(xs.shape) + 1
6967

70-
with Timer("stack"):
71-
# (nx*ny, 3) array of points, to be indexed by quads
72-
xyzs = np.stack([xs, ys, zs]).transpose(1, 2, 0).reshape(-1, 3)
73-
74-
# (nx,ny) array of numbers from 0 to n-1 that are
75-
# indexes into xyzs array for corresponding vertex
76-
inxs = np.arange(math.prod(xs.shape)).reshape(xs.shape)
68+
for function in compiled_functions:
7769

78-
# shift inxs array four different ways and stack to form
79-
# (4, nx-1, ny-1) array of quads represented as indexes into xyzs array
80-
quads = np.stack(
81-
[inxs[:-1, :-1], inxs[:-1, 1:], inxs[1:, 1:], inxs[1:, :-1]]
82-
)
70+
# compute zs from xs and ys using compiled function
71+
with Timer("compute zs"):
72+
zs = function(**{str(names[0]): xs, str(names[1]): ys})
8373

84-
# transpose and flatten to ((nx-1)*(ny-1), 4) array, suitable for use in GraphicsComplex
85-
quads = quads.T.reshape(-1, 4)
74+
# sometimes expr gets compiled into something that returns a complex
75+
# even though the imaginary part is 0
76+
# TODO: check that imag is all 0?
77+
# TODO: needed this for Hypergeometric - look into that
78+
# assert np.all(np.isreal(zs)), "array contains complex values"
79+
zs = np.real(zs)
8680

87-
# ugh - indexes in Polygon are 1-based
88-
quads += 1
81+
# if it's a constant, make it a full array
82+
if isinstance(zs, (float, int, complex)):
83+
zs = np.full(xs.shape, zs)
8984

90-
# choose a color
91-
rgb = palette[i%len(palette)]
92-
rgb = [c/255.0 for c in rgb]
93-
graphics.add_color(SymbolRGBColor, rgb)
94-
95-
# add a GraphicsComplex for this function
96-
graphics.add_complex(xyzs, lines=None, polys=quads)
85+
# (nx*ny, 3) array of points, to be indexed by quads
86+
xyzs = np.stack([xs, ys, zs]).transpose(1, 2, 0).reshape(-1, 3)
9787

88+
yield xyzs, inxs
89+
90+
# generate the quads and emit a GraphicsComplex containing them
91+
for i, (xyzs, inxs) in enumerate(compute_over_grid(*plot_options.plotpoints)):
92+
93+
# shift inxs array four different ways and stack to form
94+
# (4, nx-1, ny-1) array of quads represented as indexes into xyzs array
95+
quads = np.stack(
96+
[inxs[:-1, :-1], inxs[:-1, 1:], inxs[1:, 1:], inxs[1:, :-1]]
97+
)
98+
99+
# transpose and flatten to ((nx-1)*(ny-1), 4) array, suitable for use in GraphicsComplex
100+
quads = quads.T.reshape(-1, 4)
101+
102+
# choose a color
103+
rgb = palette[i%len(palette)]
104+
rgb = [c/255.0 for c in rgb]
105+
#graphics.add_color(SymbolRGBColor, rgb)
106+
graphics.add_directives([SymbolRGBColor, *rgb])
107+
108+
# add a GraphicsComplex for this function
109+
graphics.add_complex(xyzs, lines=None, polys=quads)
110+
111+
# if requested by the Mesh attribute create a mesh of lines covering the surfaces
112+
if nmesh:
113+
114+
# meshes are black for now
115+
graphics.add_directives([SymbolRGBColor, 0,0,0])
116+
117+
with Timer("Mesh"):
118+
nmesh = 20 # TODO: use supplied option
119+
nx, ny = plot_options.plotpoints
120+
# Do nmesh lines in each direction.
121+
# Each mesh line has high res (nx or ny) so it follows
122+
# the contours of the surface.
123+
for xyzs, inxs in compute_over_grid(nx, nmesh):
124+
graphics.add_complex(xyzs, lines=inxs, polys=None)
125+
for xyzs, inxs in compute_over_grid(nmesh, ny):
126+
graphics.add_complex(xyzs, lines=inxs.T, polys=None)
98127

99128
return graphics
100129

mathics/eval/drawing/util.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44

55

66
from mathics.core.atoms import NumericArray
7-
from mathics.core.convert.expression import to_mathics_list
7+
from mathics.core.convert.expression import to_mathics_list, to_expression
88
from mathics.core.expression import Expression
99
from mathics.core.list import ListExpression
10+
from mathics.core.symbols import Symbol
1011
from mathics.core.systemsymbols import (
1112
SymbolGraphics,
1213
SymbolGraphics3D,
@@ -59,17 +60,28 @@ def add_linexyzs(self, line_xyzs, colors=None):
5960
self.add_thing(SymbolLine, line_xyzs, colors)
6061

6162
def add_color(self, symbol, components):
62-
from mathics.core.convert.expression import to_expression
6363
expr = to_expression(symbol, *components)
6464
self.graphics.append(expr)
6565

66+
def add_directives(self, *ds):
67+
def cvt(d):
68+
if isinstance(d, list) and len(d) > 0 and isinstance(d[0], Symbol):
69+
expr = to_expression(d[0], *(cvt(dd) for dd in d[1:]))
70+
return expr
71+
else:
72+
return d
73+
for d in ds:
74+
expr = cvt(d)
75+
self.graphics.append(expr)
76+
77+
6678
def add_complex(self, xyzs, lines=None, polys=None):
6779
complex = [NumericArray(xyzs)]
6880
if polys is not None:
6981
polys_expr = Expression(SymbolPolygon, NumericArray(polys))
7082
complex.append(polys_expr)
7183
if lines is not None:
72-
polys_expr = Expression(SymbolLines, NumericArray(lines))
84+
lines_expr = Expression(SymbolLine, NumericArray(lines))
7385
complex.append(lines_expr)
7486
gc_expr = Expression(SymbolGraphicsComplex, *complex)
7587
self.graphics.append(gc_expr)

0 commit comments

Comments
 (0)