Skip to content

Commit 8ea2c48

Browse files
committed
load symbols and tests
1 parent bc66704 commit 8ea2c48

File tree

8 files changed

+335
-15
lines changed

8 files changed

+335
-15
lines changed

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ PYTHON ?= python3
99
PIP ?= pip3
1010
RM ?= rm
1111
LANG = en
12+
PYTEST_OPTIONS ?=
13+
1214

1315
.PHONY: all build \
1416
check clean \
@@ -52,7 +54,7 @@ doctest:
5254
MATHICS_CHARACTER_ENCODING="ASCII" $(PYTHON) -m mathics.docpipeline -l pymathics.vectorizedplot -c 'Mathics3 vectorizedplot Module' $o
5355
#: Run py.test tests. Use environment variable "o" for pytest options
5456
pytest:
55-
MATHICS_CHARACTER_ENCODING="ASCII" py.test test $o
57+
MATHICS_CHARACTER_ENCODING="ASCII" py.test test $(PYTEST_OPTIONS)
5658

5759

5860

pymathics/vectorizedplot/__init__.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@
2323
"""
2424

2525

26-
# from pymathics.vectorizedplot.plot_plot3d.py import SphericalPlot3D
26+
from pymathics.vectorizedplot.plot_plot3d import ContourPlot3D, ParametricPlot3D, SphericalPlot3D
2727
from pymathics.vectorizedplot.version import __version__
2828

29-
__all__ = ("__version__",
30-
#"SphericalPlot3D",
31-
"pymathics_version_data")
29+
__all__ = (
30+
"ContourPlot3D",
31+
"ParametricPlot3D",
32+
"SphericalPlot3D",
33+
"__version__",
34+
"pymathics_version_data"
35+
)
3236

3337
# To be recognized as an external mathics module, the following variable
3438
# is required:
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
"""
2+
Color functions
3+
4+
"""
5+
6+
from typing import Optional
7+
8+
import palettable
9+
10+
from mathics.core.atoms import Integer0, Integer1, MachineReal, String
11+
from mathics.core.convert.expression import to_expression
12+
from mathics.core.element import BaseElement
13+
from mathics.core.evaluation import Evaluation
14+
from mathics.core.expression import Expression
15+
from mathics.core.list import ListExpression
16+
from mathics.core.symbols import Symbol
17+
from mathics.core.systemsymbols import (
18+
SymbolBlend,
19+
SymbolColorData,
20+
SymbolFunction,
21+
SymbolMap,
22+
SymbolRGBColor,
23+
SymbolSlot,
24+
)
25+
26+
SymbolColorDataFunction = Symbol("ColorDataFunction")
27+
28+
29+
class _GradientColorScheme:
30+
def colors(self) -> list:
31+
"""return the list of colors"""
32+
raise NotImplementedError
33+
34+
def color_data_function(self, name: str) -> Expression:
35+
"""Return a function that compute the colors"""
36+
colors = ListExpression(
37+
*[
38+
to_expression(
39+
SymbolRGBColor, *color, elements_conversion_fn=MachineReal
40+
)
41+
for color in self.colors()
42+
]
43+
)
44+
blend = Expression(
45+
SymbolFunction,
46+
Expression(SymbolBlend, colors, Expression(SymbolSlot, Integer1)),
47+
)
48+
arguments = [
49+
String(name),
50+
String("Gradients"),
51+
ListExpression(Integer0, Integer1),
52+
blend,
53+
]
54+
return Expression(SymbolColorDataFunction, *arguments)
55+
56+
57+
class _PalettableGradient(_GradientColorScheme):
58+
def __init__(self, palette, is_reversed: bool):
59+
self.palette = palette
60+
self.reversed = is_reversed
61+
62+
def colors(self) -> list:
63+
colors = self.palette.mpl_colors
64+
if self.reversed:
65+
colors = list(reversed(colors))
66+
return colors
67+
68+
69+
class _PredefinedGradient(_GradientColorScheme):
70+
_colors: list
71+
72+
def __init__(self, colors: list):
73+
self._colors = colors
74+
75+
def colors(self) -> list:
76+
return self._colors
77+
78+
79+
COLOR_PALETTES = {
80+
"LakeColors": _PredefinedGradient(
81+
[
82+
(0.293416, 0.0574044, 0.529412),
83+
(0.563821, 0.527565, 0.909499),
84+
(0.762631, 0.846998, 0.914031),
85+
(0.941176, 0.906538, 0.834043),
86+
]
87+
),
88+
"Pastel": _PalettableGradient(palettable.colorbrewer.qualitative.Pastel1_9, False),
89+
"Rainbow": _PalettableGradient(palettable.colorbrewer.diverging.Spectral_9, True),
90+
"RedBlueTones": _PalettableGradient(
91+
palettable.colorbrewer.diverging.RdBu_11, False
92+
),
93+
"GreenPinkTones": _PalettableGradient(
94+
palettable.colorbrewer.diverging.PiYG_9, False
95+
),
96+
"GrayTones": _PalettableGradient(palettable.colorbrewer.sequential.Greys_9, False),
97+
"SolarColors": _PalettableGradient(palettable.colorbrewer.sequential.OrRd_9, True),
98+
"CherryTones": _PalettableGradient(palettable.colorbrewer.sequential.Reds_9, True),
99+
"FuchsiaTones": _PalettableGradient(palettable.colorbrewer.sequential.RdPu_9, True),
100+
"SiennaTones": _PalettableGradient(
101+
palettable.colorbrewer.sequential.Oranges_9, True
102+
),
103+
# specific to Mathics
104+
"Paired": _PalettableGradient(palettable.colorbrewer.qualitative.Paired_9, False),
105+
"Accent": _PalettableGradient(palettable.colorbrewer.qualitative.Accent_8, False),
106+
"Aquatic": _PalettableGradient(palettable.wesanderson.Aquatic1_5, False),
107+
"Zissou": _PalettableGradient(palettable.wesanderson.Zissou_5, False),
108+
"Tableau": _PalettableGradient(palettable.tableau.Tableau_20, False),
109+
"TrafficLight": _PalettableGradient(palettable.tableau.TrafficLight_9, False),
110+
"Moonrise1": _PalettableGradient(palettable.wesanderson.Moonrise1_5, False),
111+
}
112+
113+
114+
def get_color_palette(name, evaluation):
115+
palette = COLOR_PALETTES.get(name, None)
116+
if palette is None:
117+
evaluation.message("ColorData", "notent", name)
118+
return None
119+
return palette.colors()
120+
121+
122+
def gradient_palette(
123+
color_function: BaseElement, n: int, evaluation: Evaluation
124+
) -> Optional[list]: # always returns RGB values
125+
"""Return a list of rgb color components"""
126+
if isinstance(color_function, String):
127+
color_data = Expression(SymbolColorData, color_function).evaluate(evaluation)
128+
if not color_data.has_form("ColorDataFunction", 4):
129+
return None
130+
_, kind, interval, blend = color_data.elements
131+
if not isinstance(kind, String) or kind.get_string_value() != "Gradients":
132+
return None
133+
if not interval.has_form("List", 2):
134+
return None
135+
x0, x1 = (x.round_to_float() for x in interval.elements)
136+
else:
137+
blend = color_function
138+
x0 = 0.0
139+
x1 = 1.0
140+
141+
xd = x1 - x0
142+
offsets = [MachineReal(x0 + float(xd * i) / float(n - 1)) for i in range(n)]
143+
colors = Expression(SymbolMap, blend, ListExpression(*offsets)).evaluate(evaluation)
144+
if len(colors.elements) != n:
145+
return None
146+
147+
from mathics.builtin.colors.color_directives import ColorError, expression_to_color
148+
149+
try:
150+
objects = [expression_to_color(x) for x in colors.elements]
151+
if any(x is None for x in objects):
152+
return None
153+
return [x.to_rgba()[:3] for x in objects]
154+
except ColorError:
155+
return None
156+
157+
158+
# color-blind friendly palette from https://davidmathlogic.com/colorblind
159+
# palette3 is for 3d plots, i.e. surfaces
160+
palette3 = [
161+
(255, 176, 0), # orange
162+
(100, 143, 255), # blue
163+
(220, 38, 127), # red
164+
(50, 150, 140), # green
165+
(120, 94, 240), # purple
166+
(254, 97, 0), # dark orange
167+
(0, 114, 178), # dark blue
168+
]
169+
170+
171+
# palette 2 is for 2d plots, i.e. lines
172+
# same colors as palette3 but in a little different order
173+
palette2 = [
174+
(100, 143, 255), # blue
175+
(255, 176, 0), # orange
176+
(50, 150, 140), # green
177+
(220, 38, 127), # red
178+
(120, 94, 240), # purple
179+
(254, 97, 0), # dark orange
180+
(0, 114, 178), # dark blue
181+
]
182+
183+
184+
def palette_color_directive(palette, i, opacity=None):
185+
"""returns a directive in a form suitable for GraphicsGenerator.add_directives"""
186+
""" for setting the color of an entire entity such as a line or surface """
187+
rgb = palette[i % len(palette)]
188+
rgb = [c / 255.0 for c in rgb]
189+
if opacity is not None:
190+
rgb.append(opacity)
191+
return [SymbolRGBColor, *rgb]

pymathics/vectorizedplot/eval/plot3d_vectorized.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
from mathics.builtin.colors.color_internals import convert_color
1111
from mathics.core.evaluation import Evaluation
1212
from mathics.core.expression import Expression
13-
from mathics.core.symbols import strip_context
13+
from mathics.core.symbols import strip_context, Symbol
1414
from mathics.core.systemsymbols import (
15-
SymbolAbsoluteThickness,
15+
# TODO: uncomment me after adding this symbol to mathics.core.systemsymbols
16+
# SymbolAbsoluteThickness,
1617
SymbolEqual,
1718
SymbolFull,
1819
SymbolNone,
@@ -25,6 +26,10 @@
2526
from .util import GraphicsGenerator, compile_exprs
2627

2728

29+
# TODO: remove me after adding this symbol to mathics.core.systemsymbols
30+
SymbolAbsoluteThickness= Symbol("System`AbsoluteThickness")
31+
32+
2833
def make_surfaces(
2934
plot_options, evaluation: Evaluation, dim: int, is_complex: bool, emit
3035
):
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""
2+
Common utilities for plotting
3+
"""
4+
5+
from typing import Sequence
6+
7+
from mathics.core.atoms import NumericArray
8+
from mathics.core.convert.expression import to_expression, to_mathics_list
9+
from mathics.core.convert.lambdify import lambdify_compile
10+
from mathics.core.evaluation import Evaluation
11+
from mathics.core.expression import Expression
12+
from mathics.core.list import ListExpression
13+
from mathics.core.symbols import Symbol
14+
from mathics.core.systemsymbols import (
15+
SymbolGraphics,
16+
SymbolGraphics3D,
17+
SymbolGraphicsComplex,
18+
SymbolLine,
19+
SymbolPolygon,
20+
SymbolRGBColor,
21+
SymbolRule,
22+
SymbolVertexColors,
23+
)
24+
25+
26+
class GraphicsGenerator:
27+
"""
28+
Support for generating Graphics and Graphics3D expressions
29+
"""
30+
31+
# TODO: more precise typing?
32+
graphics: list
33+
34+
# 2 or 3
35+
dim: int
36+
37+
def __init__(self, dim: int):
38+
self.dim = dim
39+
self.graphics = []
40+
41+
# add polygons and lines, optionally with vertex colors
42+
def add_thing(self, thing_symbol, things, colors):
43+
arg = tuple(to_mathics_list(*thing) for thing in things)
44+
arg = ListExpression(*arg) if len(arg) > 1 else arg[0]
45+
if colors:
46+
color_arg = tuple(to_mathics_list(*color) for color in colors)
47+
color_arg = (
48+
ListExpression(*color_arg) if len(color_arg) > 1 else color_arg[0]
49+
)
50+
color_rule = Expression(SymbolRule, SymbolVertexColors, color_arg)
51+
self.graphics.append(Expression(thing_symbol, arg, color_rule))
52+
else:
53+
self.graphics.append(Expression(thing_symbol, arg))
54+
55+
def add_polyxyzs(self, poly_xyzs, colors=None):
56+
"""Add polygons specified by explicit xy[z] coordinates"""
57+
self.add_thing(SymbolPolygon, poly_xyzs, colors)
58+
59+
def add_linexyzs(self, line_xyzs, colors=None):
60+
"""Add lines specified by explicit xy[z] coordinates"""
61+
self.add_thing(SymbolLine, line_xyzs, colors)
62+
63+
def add_color(self, symbol, components):
64+
expr = to_expression(symbol, *components)
65+
self.graphics.append(expr)
66+
67+
def add_directives(self, *ds):
68+
def cvt(d):
69+
if isinstance(d, list) and len(d) > 0 and isinstance(d[0], Symbol):
70+
expr = to_expression(d[0], *(cvt(dd) for dd in d[1:]))
71+
return expr
72+
else:
73+
return d
74+
75+
for d in ds:
76+
expr = cvt(d)
77+
self.graphics.append(expr)
78+
79+
def add_complex(self, xyzs, lines=None, polys=None, colors=None):
80+
complex = [NumericArray(xyzs)]
81+
if polys is not None:
82+
polys_expr = Expression(SymbolPolygon, NumericArray(polys))
83+
complex.append(polys_expr)
84+
if lines is not None:
85+
lines_expr = Expression(SymbolLine, NumericArray(lines))
86+
complex.append(lines_expr)
87+
if colors is not None:
88+
colors_expr = Expression(SymbolRGBColor, NumericArray(colors))
89+
rule_expr = Expression(SymbolRule, SymbolVertexColors, colors_expr)
90+
complex.append(rule_expr)
91+
gc_expr = Expression(SymbolGraphicsComplex, *complex)
92+
self.graphics.append(gc_expr)
93+
94+
def generate(self, options):
95+
"""
96+
Generates Graphics[3D] expression from supplied lines, polygons (etc.)
97+
"""
98+
graphics_expr = Expression(
99+
SymbolGraphics3D if self.dim == 3 else SymbolGraphics,
100+
ListExpression(*self.graphics),
101+
*options,
102+
)
103+
104+
return graphics_expr
105+
106+
107+
def compile_exprs(
108+
evaluation: Evaluation, expr_or_list: Expression | Sequence, names: Sequence[str]
109+
):
110+
"""Traverse a nested list structure and compile the functions at the leaves"""
111+
if isinstance(expr_or_list, (list, tuple)):
112+
return [compile_exprs(evaluation, e, names) for e in expr_or_list]
113+
else:
114+
return lambdify_compile(evaluation, expr_or_list, names)

pymathics/vectorizedplot/plot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
SymbolStyle,
3434
)
3535
from mathics.eval.drawing.colors import COLOR_PALETTES, get_color_palette
36-
from pymathics.vectorizedplot.eval.drawing.plot import get_plot_range
36+
from pymathics.vectorizedplot.eval.plot import get_plot_range
37+
import pymathics.vectorizedplot.eval.plot3d_vectorized as plot_module
3738
from mathics.eval.nevaluator import eval_N
3839

3940
# The vectorized plot function generates GraphicsComplex using NumericArray,
@@ -51,7 +52,6 @@
5152
# depending on whether vectorized plot functions are enabled
5253
def get_plot_eval_function(cls):
5354
function_name = "eval_" + cls.__name__
54-
plot_module = pymathics.vectorizedplot.eval.drawing.plot3d_vectorized
5555
fun = getattr(plot_module, function_name)
5656
return fun
5757

0 commit comments

Comments
 (0)