Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
455e428
Update issue templates
bdlucas1 Nov 7, 2025
c8b277c
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 9, 2025
bbaccf5
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 12, 2025
9db162f
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 15, 2025
3e897a1
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 16, 2025
4f17008
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 18, 2025
e98c73d
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 18, 2025
7af7d4b
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 23, 2025
81c8cb8
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 24, 2025
16f5029
Revert "Update issue templates"
bdlucas1 Nov 24, 2025
4f6d96f
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 25, 2025
16be55c
Update Combinatorica-repo
bdlucas1 Nov 25, 2025
111a5ca
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 25, 2025
e0d0ca0
Merge remote-tracking branch 'upstream/master'
bdlucas1 Nov 27, 2025
e57d5d9
Merge remote-tracking branch 'upstream/master'
bdlucas1 Dec 1, 2025
c0e3a47
Initial take on ContourPlot
bdlucas1 Dec 5, 2025
55525f2
Handle Automatic PlotRanges
bdlucas1 Dec 5, 2025
8e325a2
Fill out PlotRange when passing from Plot* to Graphics*
bdlucas1 Dec 5, 2025
4ceb4b1
Update plot3d.py
bdlucas1 Dec 5, 2025
fb2c41c
Merge branch 'master' into ContourPlot
bdlucas1 Dec 5, 2025
2a8d99e
Update Combinatorica-repo
bdlucas1 Dec 5, 2025
16cea5b
Fix test failure
bdlucas1 Dec 5, 2025
fb2b165
Don't copy ComplexPlot* ranges to PlotRange
bdlucas1 Dec 6, 2025
1d3d9e1
Fix PlotRange calculation
bdlucas1 Dec 6, 2025
4905105
Fix error message for missing required module
bdlucas1 Dec 6, 2025
97242e9
Formatting
bdlucas1 Dec 6, 2025
7843824
Add Contours option
bdlucas1 Dec 6, 2025
e25eca9
Update comment
bdlucas1 Dec 6, 2025
0773f42
Formatting
bdlucas1 Dec 6, 2025
833d790
Remove debugging code
bdlucas1 Dec 6, 2025
28e1fc5
Little simpler. slightly faster
bdlucas1 Dec 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SYMBOLS_MANIFEST.txt
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ System`Context
System`Contexts
System`Continue
System`ContinuedFraction
System`ContourPlot
System`Convert`B64Dump`B64Decode
System`Convert`B64Dump`B64Encode
System`ConvertersDump`$ExtensionMappings
Expand Down
13 changes: 12 additions & 1 deletion mathics/builtin/box/graphics3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,17 @@ def _prepare_elements(self, elements, options, max_width=None):
self.background_color = elements.background_color

def calc_dimensions(final_pass=True):
# TODO: the code below is broken in any other case but Automatic
# because it calls elements.translate which is not implemented.
# Plots may pass specific plot ranges, triggering this deficiency
# and causing tests to fail The following line avoids this,
# and it should not change the behavior of any case which did
# previously fail with an exception.
#
# This code should be DRYed (together with the very similar code
# for the 2d case), and the missing .translate method added.
plot_range = ["System`Automatic"] * 3

if "System`Automatic" in plot_range:
xmin, xmax, ymin, ymax, zmin, zmax = elements.extent()
else:
Expand Down Expand Up @@ -291,7 +302,7 @@ def calc_dimensions(final_pass=True):
elif zmin == zmax:
zmin -= 1
zmax += 1
elif isinstance(plot_range[1], list) and len(plot_range[1]) == 2:
elif isinstance(plot_range[1], list) and len(plot_range[2]) == 2:
zmin, zmax = list(map(float, plot_range[2]))
zmin = elements.translate((0, 0, zmin))[2]
zmax = elements.translate((0, 0, zmax))[2]
Expand Down
80 changes: 79 additions & 1 deletion mathics/builtin/drawing/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
SymbolLine,
SymbolLog10,
SymbolNone,
SymbolPlotRange,
SymbolRGBColor,
SymbolSequence,
SymbolStyle,
Expand Down Expand Up @@ -67,13 +68,15 @@
from mathics.eval.drawing.plot3d_vectorized import (
eval_ComplexPlot,
eval_ComplexPlot3D,
eval_ContourPlot,
eval_DensityPlot,
eval_Plot3D,
)
else:
from mathics.eval.drawing.plot3d import (
eval_ComplexPlot,
eval_ComplexPlot3D,
eval_ContourPlot,
eval_DensityPlot,
eval_Plot3D,
)
Expand Down Expand Up @@ -467,7 +470,7 @@ def error(self, what, *args, **kwargs):
def __init__(self, expr, range_exprs, options, evaluation):
self.evaluation = evaluation

# plot ranges
# plot ranges of the form {x,xmin,xmax} etc.
self.ranges = []
for range_expr in range_exprs:
if not range_expr.has_form("List", 3):
Expand All @@ -488,6 +491,19 @@ def __init__(self, expr, range_exprs, options, evaluation):
self.error(expr, "invrange", range_expr)
self.ranges.append(range)

# Contours option
contours = expr.get_option(options, "Contours", evaluation)
if contours is not None:
c = contours.to_python()
if not (
c == "System`Automatic"
or isinstance(c, int)
or isinstance(c, tuple)
and all(isinstance(cc, (int, float)) for cc in c)
):
self.error(expr, "invcontour", contours)
self.contours = c

# Mesh option
mesh = expr.get_option(options, "Mesh", evaluation)
if mesh not in (SymbolNone, SymbolFull, SymbolAll):
Expand Down Expand Up @@ -579,6 +595,9 @@ class _Plot3D(Builtin):
"Plot range `1` must be of the form {variable, min, max}, "
"where max > min."
),
"invcontour": (
"Contours option must be Automatic, an integer, or a list of numbers."
),
}

# Plot3D, ComplexPlot3D
Expand Down Expand Up @@ -633,6 +652,39 @@ def eval(
graphics = self.eval_function(plot_options, evaluation)
if not graphics:
return

# Expand PlotRange option using the {x,xmin,xmax} etc. range specifications
# Pythonize it, so Symbol becomes str, numeric becomes int or float
plot_range = self.get_option(options, str(SymbolPlotRange), evaluation)
plot_range = plot_range.to_python()
dim = 3 if self.graphics_class is Graphics3D else 2
if isinstance(plot_range, str):
# PlotRange -> Automatic becomes PlotRange -> {Automatic, ...}
plot_range = [str(SymbolAutomatic)] * dim
if isinstance(plot_range, (int, float)):
# PlotRange -> s becomes PlotRange -> {Automatic,...,{-s,s}}
pr = plot_range
plot_range = [str(SymbolAutomatic)] * dim
plot_range[-1] = [-pr, pr]
elif isinstance(plot_range, (list, tuple)) and isinstance(
plot_range[0], (int, float)
):
# PlotRange -> {s0,s1} becomes PlotRange -> {Automatic,...,{s0,s1}}
pr = plot_range
plot_range = [str(SymbolAutomatic)] * dim
plot_range[-1] = pr

# now we have a list of length dim
# handle Automatic ~ {xmin,xmax} etc.
for i, (pr, r) in enumerate(zip(plot_range, plot_options.ranges)):
# TODO: this treats Automatic and Full as the same, which isn't quite right
if isinstance(pr, str) and not isinstance(r[1], complex):
plot_range[i] = r[1:] # extract {xmin,xmax} from {x,xmin,xmax}

# unpythonize and update PlotRange option
options[str(SymbolPlotRange)] = to_mathics_list(*plot_range)

# generate the Graphics[3D] result
graphics_expr = graphics.generate(
options_to_rules(options, self.graphics_class.options)
)
Expand Down Expand Up @@ -809,6 +861,32 @@ class ComplexPlot(_Plot3D):
graphics_class = Graphics


class ContourPlot(_Plot3D):
"""
<url>:WMA link: https://reference.wolfram.com/language/ref/ContourPlot.html</url>
<dl>
<dt>'Contour'[$f$, {$x$, $x_{min}$, $x_{max}$}, {$y$, $y_{min}$, $y_{max}$}]
<dd>creates a two-dimensional contour plot ofh $f$ over the region
$x$ ranging from $x_{min}$ to $x_{max}$ and $y$ ranging from $y_{min}$ to $y_{max}$.

See <url>:Drawing Option and Option Values:
/doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values
</url> for a list of Plot options.
</dl>

"""

requires = ["skimage"]
summary_text = "creates a contour plot"
expected_args = 3
options = _Plot3D.options2d | {"Contours": "Automatic"}
# TODO: other options?

many_functions = True
eval_function = staticmethod(eval_ContourPlot)
graphics_class = Graphics


class DensityPlot(_Plot3D):
"""
<url>:WMA link: https://reference.wolfram.com/language/ref/DensityPlot.html</url>
Expand Down
2 changes: 2 additions & 0 deletions mathics/core/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,12 +852,14 @@ class UnavailableFunction:

def __init__(self, builtin):
self.name = builtin.get_name()
self.requires = builtin.requires

def __call__(self, **kwargs):
kwargs["evaluation"].message(
"General",
"pyimport", # see messages.py for error message definition
strip_context(self.name),
", ".join(self.requires),
)


Expand Down
1 change: 1 addition & 0 deletions mathics/core/systemsymbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@
SymbolPiecewise = Symbol("System`Piecewise")
SymbolPlot = Symbol("System`Plot")
SymbolPlotLabel = Symbol("System`PlotLabel")
SymbolPlotRange = Symbol("System`PlotRange")
SymbolPlotRangeClipping = Symbol("System`PlotRangeClipping")
SymbolPlotRegion = Symbol("System`PlotRegion")
SymbolPlus = Symbol("System`Plus")
Expand Down
7 changes: 7 additions & 0 deletions mathics/eval/drawing/plot3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,3 +506,10 @@ def eval_ComplexPlot(
evaluation: Evaluation,
):
return None


def eval_ContourPlot(
plot_options,
evaluation: Evaluation,
):
return None
Loading
Loading