Showing posts with label Sage. Show all posts
Showing posts with label Sage. Show all posts

Interact.py and Worksheet.py

Following the release of SAGE 3.1, I have began to work on published worksheets. The first thing I wanted to add was a "delete" button, but upon reading the code found no logical place to put the code for it - the HTML code in notebook.py is undocumented and very disorganized. So, to learn my way around I decided to first reogranize a bit.

Here is the header for a worksheet that I published.

Here is the same header on my development branch.

I'm not sure if anyone else will like this new header, but at least there is a logical place for the delete button, both in the UI and in the code.

Sliders and Selectors - HTML vs Javascript

In my earlier enhancement of sliders, I created a Javascript list of values for each slider, which would update while the slider is being manipulated. It worked very well at first, but then resulted in a problem: SAGE truncated the array when it was too big which resulted in a Javascript error and nothing worked. In this I responded to 2 patches: pruning the array of values and removing truncation, since other people were complaining about it as well.

Since removing the truncation, I was able to create selectors with a lot of values, say, 10000.

def _(a=[1..10000]):
But they loaded very slowly because the SAGE server was rendering 10000 lines of
<option value='n' >n</option>
What if the server instead returned more Javascript? Then the code would look like this:
var values=[1, 2, ..., 10000];
for(var i in values)
    //code to generate a select option
Which means a lot less text being generated by the server. Of course, the optimal return is
for(var i=1; i<=10000; i+=1)
    //code to generate a select option
But this involves detecting the range which may or may not be possible depending on the underlying structure.

Unreadable code in Python

I finally figured out how to write unreadable code in Python. See if you can figure out what the following code does:

@interact
def _(h=(20,(1,36,1))):
    print (lambda f:f(0,f))(
        lambda n,f:'%s\n%s'%(
            ('*'*(2*n+1)).join([' '*(h-n-1)]*2),
            ((n<h-1 and f(n+1,f)) or '')
        )
    )

Apart from the interact function overhead, the code can be compressed into one line. Motivated by a discussion about Maxima in SAGE.

Range Sliders

Here is what I was working on in the last two days.

The range slider is allows selecting a pair a,b with a≤b using one control.

This is Trac #3679, awaiting review.

Text Controls and More

A text control is just a block of text (really, HTML) that gets inserted among other controls. This can be useful in interactions that are not in context, for example by providing a brief description of what the interaction does. Unfortunately, the following generates a syntax error:

def f("This function does nothing."):
    pass

Doing nothing in Python is perfectly legal, but having a non-variable in the definition isn't. My current implementation isn't pretty:

@interact
def _(t1=text_control("This function does nothing.")):
    pass

In working on the control I have come to realize that the structure of interact is indeed limited. Currently, interact takes a function, whose variables are set with defaults as control objects. The design has several major limitations:

  1. All controls must be tied to a variable, which is not what we always want. For example, there is no reason to tie a block of text to a variable.
  2. Functions generated for interact cannot be used outside of interact. Similarly, functions generated outside of interact do not make good interactions. One has to write a wrapper function with the right controls.

Mathematica's Manipulate command uses a more logical design: pick any function and pass along a list of controls. For example:

Current

@interact
def f(t1=text_control("Plots a function."),
l=slider(-20, 0, default=-2, label='Lower bound:'),
u=slider(0,20, default=2, label='Upper bound:')):
    plot(lambda x:sin(x)/x, l, u).show()

Versus something like:

def f(l,u):
    plot(lambda x:sin(x)/x, l, u).show()

interact(f, "Plots a function",
('l', slider(-20, 0, default=-2, label='Lower bound:')),
('u', slider(0,20, default=2, label='Upper bound:')))

Benefits:

  1. We can put anything in the list: text, numbers (default values with no controls) without associating a variable.
  2. We can easily make high quality (that is, with appropriate controls, text, etc.) interactions for builtin SAGE functions or any other functions not designed for interact.
  3. We can nest interactions. Right now, it is possible to create a function, and then call interact on it, but this method is known to break because interactions are limited to one and nothing else per cell.
  4. I think it is easier to understand for someone who knows Python well but is not familiar with interact.

Well, those were my thoughts on interact right now, but perhaps I am looking at interact from the wrong angle today after messing with text controls. I will continue to perfect the text control, with another control in mind and more long-term plans that involve something completely different. To the reader I leave this mystery and hint only that the idea for the new control came to me yesterday while I was writing the example interaction.

Buttons and Sliders

Buttons, sliders, and text inputs are probably the most useful controls in everyday applications. There is also a checkbox control, but I have not yet seen that one used. And there are specialized controls, like the color selector. While I haven't done anything with text inputs, I have done a little with buttons and a little more with sliders.

Sunday

Who works on Sundays? Apparently some SAGE developers do because on Sunday I was on e-mail with William Stein trying to get to understand interact.py, the file that contains most of the Python interact code. Eventually I made a three-line change that satisfied one of the open boxes in the todo list.

The change is highlighting depressed buttons in button bars, which posed only a very little design challenge: somehow figuring out which button has been highlighted. But, JQuery comes to rescue with a very simple solution:

$("BUTTON", this.parentNode).css("border-style", "outset")

The first part tells JQuery to look for all buttons in the cell containing the button being pressed, the second part tells JQuery to make all those buttons look like they have popped up.

The patch: SAGE Trac #3578 is approved and merged with the next release of SAGE.

Monday

I started playing around with sliders, trying to dynamically assign slider width, and soon realized that is too much work for the result - for now. This is because SAGE uses visually-appealing sliders with rounded ends. So, I settled with a longer static-width slider but decided to also add a label. Like with buttons, the point is to make it easier to tell the position of the slider so the label would have to change as I move the slider, not afterwards. This, of course, meant that there must be a Javascript array containing the possible values on the slider, and they are not limited to numbers. And if you are experienced with Javascript, you'll know this trick:

(function(){
    var values = [...];
    ...
})();

This creates a block and executes it. Why? The scope of the variable sliders is limited to the block, so I can create several of these and not worry about confuscating the variable to avoid duplicates.

Here is an example of an interaction using sliders:

The patch: SAGE Trac #3599 is currently awaiting review.

SAGE - Interact

This and most of the following posts will be related to my development of SAGE's Interact feature. I will try to document my progress daily and provide useful links.