Sage Interact Quickstart

This Sage quickstart tutorial was developed for the MAA PREP Workshop “Sage: Using Open-Source Mathematics Software with Undergraduates” (funding provided by NSF DUE 0817071).

Invaluable resources are the Sage wiki http://wiki.sagemath.org/interact (type “sage interact” into Google), UTMOST Sage Cell Repository (a collection of contributed interacts).

Start with just one command

How would one create an interactive cell? First, let’s focus on a new thing to do! Perhaps we just want a graph plotter that has some options.

So let’s start by getting the commands for what you want the output to look like. Here we just want a simple plot.

sage: plot(x^2,(x,-3,3))
Graphics object consisting of 1 graphics primitive
>>> from sage.all import *
>>> plot(x**Integer(2),(x,-Integer(3),Integer(3)))
Graphics object consisting of 1 graphics primitive
plot(x^2,(x,-3,3))

Then abstract out the parts you want to change. We’ll be letting the user change the function, so let’s make that a variable f.

sage: f=x^3
sage: plot(f,(x,-3,3))
Graphics object consisting of 1 graphics primitive
>>> from sage.all import *
>>> f=x**Integer(3)
>>> plot(f,(x,-Integer(3),Integer(3)))
Graphics object consisting of 1 graphics primitive
f=x^3
plot(f,(x,-3,3))

This was important because it allowed you to step back and think about what you would really be doing.

Now for the technical part. We make this a def function - see the programming tutorial.

sage: def myplot(f=x^2):
....:     show(plot(f,(x,-3,3)))
>>> from sage.all import *
>>> def myplot(f=x**Integer(2)):
...     show(plot(f,(x,-Integer(3),Integer(3))))
def myplot(f=x^2):
    show(plot(f,(x,-3,3)))

Let’s test the def function myplot by just calling it.

sage: myplot()
>>> from sage.all import *
>>> myplot()
myplot()

If we call it with a different value for f, we should get a different plot.

sage: myplot(x^3)
>>> from sage.all import *
>>> myplot(x**Integer(3))
myplot(x^3)

So far, we’ve only defined a new function, so this was review. To make a “control” to allow the user to interactively enter the function, we just preface the function with @interact.

sage: @interact
sage: def myplot(f=x^2):
....:     show(plot(f,(x,-3,3)))
>>> from sage.all import *
>>> @interact
>>> def myplot(f=x**Integer(2)):
...     show(plot(f,(x,-Integer(3),Integer(3))))
@interact
def myplot(f=x^2):
    show(plot(f,(x,-3,3)))

Note

Technically what @interact does is wrap the function, so the above is equivalent to:

def myplot(..): ...
myplot=interact(myplot)

Note that we can still call our function, even when we’ve used @interact. This is often useful in debugging it.

sage: myplot(x^4)
>>> from sage.all import *
>>> myplot(x**Integer(4))
myplot(x^4)

Adding Complexity

We can go ahead and replace other parts of the expression with variables. Note that _ is the function name now. That is a just convention for throw-away names that we don’t care about.

sage: @interact
sage: def _(f=x^2, a=-3, b=3):
....:     show(plot(f,(x,a,b)))
>>> from sage.all import *
>>> @interact
>>> def _(f=x**Integer(2), a=-Integer(3), b=Integer(3)):
...     show(plot(f,(x,a,b)))
@interact
def _(f=x^2, a=-3, b=3):
    show(plot(f,(x,a,b)))

If we pass ('label', default_value) in for a control, then the control gets the label when printed. Here, we’ve put in some text for all three of them. Remember that the text must be in quotes! Otherwise Sage will think that you are referring (for example) to some variable called “lower”, which it will think you forgot to define.

sage: @interact
sage: def _(f=('$f$', x^2), a=('lower', -3), b=('upper', 3)):
....:     show(plot(f,(x,a,b)))
>>> from sage.all import *
>>> @interact
>>> def _(f=('$f$', x**Integer(2)), a=('lower', -Integer(3)), b=('upper', Integer(3))):
...     show(plot(f,(x,a,b)))
@interact
def _(f=('$f$', x^2), a=('lower', -3), b=('upper', 3)):
    show(plot(f,(x,a,b)))

We can specify the type of control explicitly, along with options. See below for more detail on the possibilities.

sage: @interact
sage: def _(f=input_box(x^2, width=20, label="$f$")):
....:     show(plot(f,(x,-3,3)))
>>> from sage.all import *
>>> @interact
>>> def _(f=input_box(x**Integer(2), width=Integer(20), label="$f$")):
...     show(plot(f,(x,-Integer(3),Integer(3))))
@interact
def _(f=input_box(x^2, width=20, label="$f$")):
    show(plot(f,(x,-3,3)))

Here we demonstrate a bunch of options. Notice the new controls:

  • range_slider, which passes in two values, zoom[0] and zoom[1]

  • True/False gets converted to checkboxes for the end user

sage: @interact
sage: def _(f=input_box(x^2,width=20),
....: color=color_selector(widget='colorpicker', label=""),
....: axes=True,
....: fill=True,
....: zoom=range_slider(-3,3,default=(-3,3))):
....:     show(plot(f,(x,zoom[0], zoom[1]), color=color, axes=axes,fill=fill))
>>> from sage.all import *
>>> @interact
>>> def _(f=input_box(x**Integer(2),width=Integer(20)),
... color=color_selector(widget='colorpicker', label=""),
... axes=True,
... fill=True,
... zoom=range_slider(-Integer(3),Integer(3),default=(-Integer(3),Integer(3)))):
...     show(plot(f,(x,zoom[Integer(0)], zoom[Integer(1)]), color=color, axes=axes,fill=fill))
@interact
def _(f=input_box(x^2,width=20),
color=color_selector(widget='colorpicker', label=""),
axes=True,
fill=True,
zoom=range_slider(-3,3,default=(-3,3))):
    show(plot(f,(x,zoom[0], zoom[1]), color=color, axes=axes,fill=fill))

There is also one button type to disable automatic updates.

The previous interact was a bit ugly, because all of the controls were stacked on top of each other. We can control the layout of the widget controls in a grid (at the top, bottom, left, or right) using the layout parameter.

sage: @interact(layout=dict(top=[['f', 'color']],
....: left=[['axes'],['fill']],
....: bottom=[['zoom']]))
sage: def _(f=input_box(x^2,width=20),
....: color=color_selector(widget='colorpicker', label=""),
....: axes=True,
....: fill=True,
....: zoom=range_slider(-3,3, default=(-3,3))):
....:     show(plot(f,(x,zoom[0], zoom[1]), color=color, axes=axes,fill=fill))
>>> from sage.all import *
>>> @interact(layout=dict(top=[['f', 'color']],
... left=[['axes'],['fill']],
... bottom=[['zoom']]))
>>> def _(f=input_box(x**Integer(2),width=Integer(20)),
... color=color_selector(widget='colorpicker', label=""),
... axes=True,
... fill=True,
... zoom=range_slider(-Integer(3),Integer(3), default=(-Integer(3),Integer(3)))):
...     show(plot(f,(x,zoom[Integer(0)], zoom[Integer(1)]), color=color, axes=axes,fill=fill))
@interact(layout=dict(top=[['f', 'color']],
left=[['axes'],['fill']],
bottom=[['zoom']]))
def _(f=input_box(x^2,width=20),
color=color_selector(widget='colorpicker', label=""),
axes=True,
fill=True,
zoom=range_slider(-3,3, default=(-3,3))):
    show(plot(f,(x,zoom[0], zoom[1]), color=color, axes=axes,fill=fill))

Control Types

There are many potential types of widgets one might want to use for interactive control. Sage has all of the following:

  • boxes

  • sliders

  • range sliders

  • checkboxes

  • selectors (dropdown lists or buttons)

  • grid of boxes

  • color selectors

  • plain text

We illustrate some more of these below.

sage: @interact
sage: def _(frame=checkbox(True, label='Use frame')):
....:     show(plot(sin(x), (x,-5,5)), frame=frame)
>>> from sage.all import *
>>> @interact
>>> def _(frame=checkbox(True, label='Use frame')):
...     show(plot(sin(x), (x,-Integer(5),Integer(5))), frame=frame)
@interact
def _(frame=checkbox(True, label='Use frame')):
    show(plot(sin(x), (x,-5,5)), frame=frame)
sage: var('x,y')
sage: colormaps=sage.plot.colors.colormaps.keys()
sage: @interact
sage: def _(cmap=selector(colormaps)):
....:     contour_plot(x^2-y^2,(x,-2,2),(y,-2,2),cmap=cmap).show()
>>> from sage.all import *
>>> var('x,y')
>>> colormaps=sage.plot.colors.colormaps.keys()
>>> @interact
>>> def _(cmap=selector(colormaps)):
...     contour_plot(x**Integer(2)-y**Integer(2),(x,-Integer(2),Integer(2)),(y,-Integer(2),Integer(2)),cmap=cmap).show()
var('x,y')
colormaps=sage.plot.colors.colormaps.keys()
@interact
def _(cmap=selector(colormaps)):
    contour_plot(x^2-y^2,(x,-2,2),(y,-2,2),cmap=cmap).show()
sage: var('x,y')
sage: colormaps=sage.plot.colors.colormaps.keys()
sage: @interact
sage: def _(cmap=selector(['RdBu', 'jet', 'gray','gray_r'],buttons=True),
sage: type=['density','contour']):
....:     if type=='contour':
....:         contour_plot(x^2-y^2,(x,-2,2),(y,-2,2),cmap=cmap, aspect_ratio=1).show()
....:     else:
....:         density_plot(x^2-y^2,(x,-2,2),(y,-2,2),cmap=cmap, frame=True,axes=False,aspect_ratio=1).show()
>>> from sage.all import *
>>> var('x,y')
>>> colormaps=sage.plot.colors.colormaps.keys()
>>> @interact
>>> def _(cmap=selector(['RdBu', 'jet', 'gray','gray_r'],buttons=True),
>>> type=['density','contour']):
...     if type=='contour':
...         contour_plot(x**Integer(2)-y**Integer(2),(x,-Integer(2),Integer(2)),(y,-Integer(2),Integer(2)),cmap=cmap, aspect_ratio=Integer(1)).show()
...     else:
...         density_plot(x**Integer(2)-y**Integer(2),(x,-Integer(2),Integer(2)),(y,-Integer(2),Integer(2)),cmap=cmap, frame=True,axes=False,aspect_ratio=Integer(1)).show()
var('x,y')
colormaps=sage.plot.colors.colormaps.keys()
@interact
def _(cmap=selector(['RdBu', 'jet', 'gray','gray_r'],buttons=True),
type=['density','contour']):
    if type=='contour':
        contour_plot(x^2-y^2,(x,-2,2),(y,-2,2),cmap=cmap, aspect_ratio=1).show()
    else:
        density_plot(x^2-y^2,(x,-2,2),(y,-2,2),cmap=cmap, frame=True,axes=False,aspect_ratio=1).show()

By default, ranges are sliders that divide the range into 50 steps.

sage: @interact
sage: def _(n=(1,20)):
....:     print(factorial(n))
>>> from sage.all import *
>>> @interact
>>> def _(n=(Integer(1),Integer(20))):
...     print(factorial(n))
@interact
def _(n=(1,20)):
    print(factorial(n))

You can set the step size to get, for example, just integer values.

sage: @interact
sage: def _(n=slider(1,20, step_size=1)):
....:     print(factorial(n))
>>> from sage.all import *
>>> @interact
>>> def _(n=slider(Integer(1),Integer(20), step_size=Integer(1))):
...     print(factorial(n))
@interact
def _(n=slider(1,20, step_size=1)):
    print(factorial(n))

Or you can explicitly specify the slider values.

sage: @interact
sage: def _(n=slider([1..20])):
....:     print(factorial(n))
>>> from sage.all import *
>>> @interact
>>> def _(n=slider((ellipsis_range(Integer(1),Ellipsis,Integer(20))))):
...     print(factorial(n))
@interact
def _(n=slider([1..20])):
    print(factorial(n))

And the slider values don’t even have to be numbers!

sage: @interact
sage: def _(fun=('function', slider([sin,cos,tan,sec,csc,cot]))):
....:     print(fun(4.39293))
>>> from sage.all import *
>>> @interact
>>> def _(fun=('function', slider([sin,cos,tan,sec,csc,cot]))):
...     print(fun(RealNumber('4.39293')))
@interact
def _(fun=('function', slider([sin,cos,tan,sec,csc,cot]))):
    print(fun(4.39293))

Matrices are automatically converted to a grid of input boxes.

sage: @interact
sage: def _(m=('matrix', identity_matrix(2))):
....:     print(m.eigenvalues())
>>> from sage.all import *
>>> @interact
>>> def _(m=('matrix', identity_matrix(Integer(2)))):
...     print(m.eigenvalues())
@interact
def _(m=('matrix', identity_matrix(2))):
    print(m.eigenvalues())

Here’s how to get vectors from a grid of boxes.

sage: @interact
sage: def _(v=('vector', input_grid(1, 3, default=[[1,2,3]], to_value=lambda x: vector(flatten(x))))):
....:     print(v.norm())
>>> from sage.all import *
>>> @interact
>>> def _(v=('vector', input_grid(Integer(1), Integer(3), default=[[Integer(1),Integer(2),Integer(3)]], to_value=lambda x: vector(flatten(x))))):
...     print(v.norm())
@interact
def _(v=('vector', input_grid(1, 3, default=[[1,2,3]], to_value=lambda x: vector(flatten(x))))):
    print(v.norm())

The option not to update

As a final problem, what happens when the controls get so complicated that it would counterproductive to see the interact update for each of the changes one wants to make? Think changing the endpoints and order of integration for a triple integral, for instance, or the example below where a whole matrix might be changed.

In this situation, where we don’t want any updates until we specifically say so, we can use the auto_update=False option. This will create a button to enable the user to update as soon as he or she is ready.

sage: @interact
sage: def _(m=('matrix', identity_matrix(2)), auto_update=False):
....:     print(m.eigenvalues())
>>> from sage.all import *
>>> @interact
>>> def _(m=('matrix', identity_matrix(Integer(2))), auto_update=False):
...     print(m.eigenvalues())
@interact
def _(m=('matrix', identity_matrix(2)), auto_update=False):
    print(m.eigenvalues())