Math 480: Sage Notebook @interact's

William Stein, 2010-04-21

Sage's "Interact": a first example

{{{id=6| @interact def f(m=range(1,13), n=(1..12)): print '%s * %s = %s '%(n,m,n*m) print ('x'*m+' ')*n /// }}}

Putting @interact before a function causes Sage to analyze the inputs to the function and their default values and create corresponding controls, such as drop down boxes, sliders, color selectors, etc.

  1. That @ is a completely new Python operator we haven't seen before.  It's a decorator.  We will learn more about it below. 
  2. The notation "(1..12)" is a special non-Python notation only available in Sage.  It means, make the iterator over numbers from 1 to 12.  The interact decorator turns iterators into range slider controls. 
  3. The range(1,13) makes the Python list [1,2,...,12], and the interact decorator turns that into a drop-down box.
  4. Selecting a value for m or n causes the function f to be re-evaluated, and the output appears below the controls.

 

{{{id=5| /// }}}

History of @Interact

  1. In January 2008, many potential Sage users were complaining on the mailing list: "If Sage can't do something like Mathematica's Manipulate[...] command, then we and our colleagues absolutely cannot switch to Sage."   See http://demonstrations.wolfram.com/.
  2. In January 2008, I talked for a while with Eric Weisstein (of "MathWorld" fame, see http://mathworld.wolfram.com/), whose job is to work on Mathematica, and he told me that their 6.0 sales were significantly up because of their new "Manipulate" feature, and hence they were hiring many new people (see my blog post from that meeting). 
  3. In February 2008, we had Sage Days 8 at Enthought in Austin, Texas.  I implemented much of Sage's analogue of Manipulate that week -- this was easily the most sleep deprived week of my life. 
  4. Igor Tolkov, a UW undergrad spent Summer 2008 funded by Google Summer of Code, improving interact (e.g., adding range sliders).
  5. Other people added some other controls, e.g., matrices. 
  6. Mitesh Patel made it so @interacts can appear in standalone web pages, in theory, but this didn't go into Sage yet, and I've never seen a working demo...    But I plan to get this working within a month.
{{{id=4| /// }}}

What's up with the @ in @Interact?  Python decorators.

Here is the simplest case, which is enough for us for now:

@dec1
def func(arg1, arg2, ...):
    pass

This is equivalent to:

def func(arg1, arg2, ...):
    pass
func = dec1(func)
{{{id=15| def chatty(f): def g(*args, **kwds): print "Function called with input args=%s, kwds=%s"%(args, kwds) return f(*args, **kwds) return g /// }}} {{{id=13| @chatty def f(n, m): return n*m /// }}} {{{id=1| f(3, m=5) /// Function called with input args=(3,), kwds={'m': 5} 15 }}}

Make sure everybody understands what I did above, as if their life depends on it. 

  1. Recall/explain: *args, **kwds
  2. Explain: Closure -- I created a function inside of another function and returned it!
  3. The "decorator" @chatty is very clean and simple.

Other Examples

These examples use some handy decorators that come with Sage.

  1. cached_function: make it so that if the function is called again with exactly the same input, then it uses a cached value of its output instead of recomputing.
  2. parallel(n): make it so calling the function with a list of inputs, runs the function in parallel on multiple processors

 

{{{id=12| @cached_function def f(n, m): print "Working really hard to multiply %s by %s..."%(n,m) return n*m /// }}} {{{id=19| f(5,7) /// Working really hard to multiply 5 by 7... 35 }}} {{{id=20| f(5,7) /// 35 }}}

Oh, you can combine multiple decorators:

{{{id=21| @cached_function @chatty def f(n, m): return n*m /// }}} {{{id=23| f(5,7) /// Function called with input args=(5, 7), kwds={} 35 }}} {{{id=24| f(5,7) /// 35 }}} {{{id=26| @parallel(2) def f(n): return factorial(n).ndigits() /// }}} {{{id=25| time f(10^6) /// 5565709 Time: CPU 0.67 s, Wall: 0.68 s }}}

Watch the walltime below:

{{{id=28| time v = [f(10^6+i) for i in [1..4]] /// Time: CPU 2.71 s, Wall: 2.96 s }}} {{{id=30| time w = list(f([10^6+i for i in [1..4]])) /// Time: CPU 0.03 s, Wall: 2.24 s }}}

Now try this on sagenb.org, which has 24 processing cores.   http://sagenb.org/home/pub/1986

{{{id=35| /// }}}

Now, back to @interact...

Type interact? for a detailed list of all the allowed controls, automatically generated controls, etc., and many examples.  Also, see this wiki page for more elaborate examples.

{{{id=34| interact? ///

File: /Users/wstein/sage/build/sage/local/lib/python2.6/site-packages/sagenb-0.7.5.3-py2.6.egg/sagenb/notebook/interact.py

Type: <type ‘function’>

Definition: interact(f)

Docstring:

Use interact as a decorator to create interactive Sage notebook cells with sliders, text boxes, radio buttons, check boxes, and color selectors. Simply put @interact on the line before a function definition in a cell by itself, and choose appropriate defaults for the variable names to determine the types of controls (see tables below).

INPUT:

EXAMPLES:

In each example below we use a single underscore for the function name. You can use any name you want; it does not have to be an underscore.

We create an interact control with two inputs, a text input for the variable a and a y slider that runs through the range of integers from 0 to 19.

sage: @interact
... def _(a=5, y=(0..20)): print a + y
...
<html>...

Draw a plot interacting with the “continuous” variable a. By default continuous variables have exactly 50 possibilities.

sage: @interact
... def _(a=(0,2)):
...     show(plot(sin(x*(1+a*x)), (x,0,6)), figsize=4)
<html>...

Interact a variable in steps of 1 (we also use an unnamed function):

sage: @interact
... def _(n=(10,100,1)):
...     show(factor(x^n - 1))
<html>...

Interact two variables:

sage: @interact
... def _(a=(1,4), b=(0,10)):
...     show(plot(sin(a*x+b), (x,0,6)), figsize=3)
<html>...

Place a block of text among the controls:

sage: @interact
... def _(t1=text_control("Factors an integer."), n="1"):
...     print factor(Integer(n))
<html>...

You do not have to use interact as a decorators; you can also simply write interact(f) where f is any Python function that you have defined, though this is frowned upon. E.g., f can also be a library function as long as it is written in Python:

sage: interact(matrix)   # put ZZ, 2,2,[1..4] in boxes...
<html>...

If your the time to evaluate your function takes awhile, you may not want to have it reevaluated every time the inputs change. In order to prevent this, you can add a keyword auto_update=False to your function to prevent it from updating whenever the values are changed. This will cause a button labeled ‘Update’ to appear which you can click on to re-evaluate your function.

sage: @interact
... def _(n=(10,100,1), auto_update=False):
...     show(factor(x^n - 1))
<html>...

DEFAULTS:

Defaults for the variables of the input function determine interactive controls. The standard controls are input_box, slider, range_slider, checkbox, selector, input_grid, and color_selector. There is also a text control (see the defaults below).

You can also create a color selector by setting the default value for an input_box to Color(...).

There are also some convenient defaults that allow you to make controls automatically without having to explicitly specify them. E.g., you can make x a continuous slider of values between u and v by just writing x=(u,v) in the argument list of your function. These are all just convenient shortcuts for creating the controls listed above.

Note

Suppose you would like to make an interactive with a default RGB color of (1,0,0), so the function would have signature f(color=(1,0,0)). Unfortunately, the above shortcuts reinterpret the (1,0,0) as a discrete slider with step size 0 between 1 and 0. Instead you should do the following:

sage: @interact
... def _(v = input_box((1,0,0))):
...       show(plot(sin,color=v))
<html>...

An alternative:

sage: @interact
... def _(c = color_selector((1, 0, 0))):
...       show(plot(sin, color = c))
<html>...

MORE EXAMPLES:

We give an input box that allows one to enter completely arbitrary strings:

sage: @interact
... def _(a=input_box('sage', label="Enter your name", type=str)):
...        print "Hello there %s"%a.capitalize()
<html>...

The scope of variables that you control via interact() are local to the scope of the function being interacted with. However, by using the global Python keyword, you can still modify global variables as follows:

sage: xyz = 10
sage: @interact
... def _(a=('xyz',5)):
...       global xyz
...       xyz = a
<html>...

If you enter the above you obtain an interact() canvas. Entering values in the box changes the global variable xyz. Here’s a example with several controls:

sage: @interact
... def _(title=["A Plot Demo", "Something silly", "something tricky"], a=input_box(sin(x*sin(x*sin(x))), 'function'),
...     clr = Color('red'), thickness=[1..30], zoom=(1,0.95,..,0.1), plot_points=(200..2000)):
...     html('<h1 align=center>%s</h1>'%title)
...     print plot_points
...     show(plot(a, -zoom*pi,zoom*pi, color=clr, thickness=thickness, plot_points=plot_points))
<html>...

For a more compact color control, use an empty label, a different widget ('colorpicker' or 'jpicker'), and hide the input box:

sage: @interact
... def _(color=color_selector((1,0,1), label='', widget='colorpicker', hide_box=True)):
...     show(plot(x/(8/7+sin(x)), (x,-50,50), fill=True, fillcolor=color))
<html>...

We give defaults and name the variables:

sage: @interact
... def _(a=('first', (1,4)), b=(0,10)):
...       show(plot(sin(a*x+sin(b*x)), (x,0,6)), figsize=3)
<html>...

Another example involving labels, defaults, and the slider command:

sage: @interact
... def _(a = slider(1, 4, default=2, label='Multiplier'),
...       b = slider(0, 10, default=0, label='Phase Variable')):
...     show(plot(sin(a*x+b), (x,0,6)), figsize=4)
<html>...

An example where the range slider control is useful:

sage: @interact
... def _(b = range_slider(-20, 20, 1, default=(-19,3), label='Range')):
...     plot(sin(x)/x, b[0], b[1]).show(xmin=b[0],xmax=b[1])
<html>...

An example using checkboxes, obtained by making the default values bools:

sage: @interact
... def _(axes=('Show axes', True), square=False):
...       show(plot(sin, -5,5), axes=axes, aspect_ratio = (1 if square else None))
<html>...

An example generating a random walk that uses a checkbox control to determine whether points are placed at each step:

sage: @interact
... def foo(pts = checkbox(True, "points"), n = (50,(10..100))):
...       s = 0; v = [(0,0)]
...       for i in range(n):
...            s += random() - 0.5
...            v.append((i, s))
...       L = line(v, rgbcolor='#4a8de2')
...       if pts: L += points(v, pointsize=20, rgbcolor='black')
...       show(L)
<html>...

You can rotate and zoom into 3-D graphics while interacting with a variable:

sage: @interact
... def _(a=(0,1)):
...     x,y = var('x,y')
...     show(plot3d(sin(x*cos(y*a)), (x,0,5), (y,0,5)), figsize=4)
<html>...

A random polygon:

sage: pts = [(random(), random()) for _ in xrange(20)]
sage: @interact
... def _(n = (4..len(pts)), c=Color('purple') ):
...     G = points(pts[:n],pointsize=60) + polygon(pts[:n], rgbcolor=c)
...     show(G, figsize=5, xmin=0, ymin=0)
<html>...

Two “sinks” displayed simultaneously via a contour plot and a 3-D interactive plot:

sage: @interact
... def _(q1=(-1,(-3,3)), q2=(-2,(-3,3))):
...     x,y = var('x,y')
...     f = q1/sqrt((x+1)^2 + y^2) + q2/sqrt((x-1)^2+(y+0.5)^2)
...     C = contour_plot(f, (-2,2), (-2,2), plot_points=30, contours=15, cmap='cool')
...     show(C, figsize=3, aspect_ratio=1)
...     show(plot3d(f, (x,-2,2), (y,-2,2)), figsize=4)
<html>...

This is similar to above, but you can select the color map from a dropdown menu:

sage: @interact
... def _(q1=(-1,(-3,3)), q2=(-2,(-3,3)),
...    cmap=['autumn', 'bone', 'cool', 'copper', 'gray', 'hot', 'hsv',
...          'jet', 'pink', 'prism', 'spring', 'summer', 'winter']):
...     x,y = var('x,y')
...     f = q1/sqrt((x+1)^2 + y^2) + q2/sqrt((x-1)^2+(y+0.5)^2)
...     C = contour_plot(f, (x,-2,2), (y,-2,2), plot_points=30, contours=15, cmap=cmap)
...     show(C, figsize=3, aspect_ratio=1)
<html>...

A quadratic roots etch-a-sketch:

sage: v = []
sage: html('<h2>Quadratic Root Etch-a-sketch</h2>')
<html>...<h2>Quadratic Root Etch-a-sketch</h2>...</html>
sage: @interact
... def _(a=[-10..10], b=[-10..10], c=[-10..10]):
...       f = a*x^2 + b*x + c == 0; show(f)
...       soln = solve(a*x^2 + b*x + c == 0, x)[0].rhs()
...       show(soln)
...       P = tuple(CDF(soln))
...       v.append(P)
...       show(line(v, rgbcolor='purple') + point(P, pointsize=200))
<html>...

In the following example, we only generate data for a given n once, so that as one varies p the data does not randomly change. We do this by simply caching the results for each n in a dictionary.:

sage: data = {}
sage: @interact
... def _(n=(500,(100,5000,1)), p=(1,(0.1,10))):
...     n = int(n)
...     if not data.has_key(n):
...         data[n] = [(random(), random()) for _ in xrange(n)]
...     show(points([(x^p,y^p) for x,y in data[n]], rgbcolor='black'), xmin=0, ymin=0, axes=False)
<html>...

A conchoid:

sage: @interact
... def _(k=(1.2,(1.1,2)), k_2=(1.2,(1.1,2)), a=(1.5,(1.1,2))):
...     u, v = var('u,v')
...     f = (k^u*(1+cos(v))*cos(u), k^u*(1+cos(v))*sin(u), k^u*sin(v)-a*k_2^u)
...     show(parametric_plot3d(f, (u,0,6*pi), (v,0,2*pi), plot_points=[40,40], texture=(0,0.5,0)))
<html>...

An input grid:

sage: @interact
... def _(A=matrix(QQ,3,3,range(9)), v=matrix(QQ,3,1,range(3))):
...     try:
...         x = A\v
...         html('$$%s %s = %s$$'%(latex(A), latex(x), latex(v)))
...     except:
...         html('There is no solution to $$%s x=%s$$'%(latex(A), latex(v)))
<html>...
}}}

For the rest of this lecture, I'll just show you some examples of using @interact, and explain them in detail.   You should read straight through interact? in order to understand @interact more deeply.

{{{id=38| @interact def _(t1=text_control("Factors an integer."), n=("Integer", 1)): print factor(Integer(n)) /// }}}

The above example shows how to put text among interact controls, and how to label a control. 

  1. Use text_control to create a "control" that is just some static text.
  2. Makes a control that sets the variable $n$.  Control is labeled "Integer" though.  In general if you set the default value of a variable to (string, something), then the control is labeled using the string, and the control itself is determined from the value of something.
{{{id=37| @interact def f(M = random_matrix(QQ,3,4)): print "The echelon form of the above matrix is:" print M.echelon_form() ///
}}}

In the above example, @interact sees that the default value of M is a 3x4 matrix of rational numbers, and makes a corresponding control.   If you change any of the entries of the matrix, then the new value is converted to a rational, and the reduced row echelon form is computed and displayed.

{{{id=31| @interact def foo(pts = checkbox(True, "points"), steps = (50,(10..100)), generate=['Generate']): import random s = 0; v = [(0,0)] for i in range(steps): s += random.normalvariate(0,1) v.append((i, s)) L = line(v) if pts: L += points(v, pointsize=10, rgbcolor='black') show(L, figsize=[8,2], frame=True) ///
points 
steps 
generate 
}}}

The above interact generates a Gaussian random walk (1-d Brownian motion).

  1. It uses a checkbox control to determine whether points are placed at each step.
  2. A slider determines how many points are computed. 
  3. The "Generate" button forces re-evaluation of the function (but otherwise does nothing).

 

{{{id=46| var('x') @interact def foo(f=sin(x)-cos(x^2), color=Color('red')): show(plot(f, -5, 5, color=color)) ///
color 
}}}

From the documentation about making a color selector:

u = color_selector(default=(0,0,1), label=None, widget='farbtastic', hide_box=False) 

         - a color selector with a possibly hidden input box; the widget can also be 'jpicker' or 'colorpicker'

{{{id=45| var('x') @interact def foo(f=sin(x)-cos(x^2), color=color_selector(widget='jpicker')): show(plot(f, -5, 5, color=color)) ///
color 
}}} {{{id=48| var('x') @interact def foo(f=sin(x)-cos(x^2), color=color_selector(widget='colorpicker')): show(plot(f, -5, 5, color=color)) ///
color 
}}}

Questions?

{{{id=51| /// }}} {{{id=50| /// }}} {{{id=49| /// }}}