### Using IPython Notebook for manual computations

(Edit July 27: please see this update)

One of the hardest organizational tasks I’ve found in the past few years is keeping track of computations. When I started as a graduate student I began by doing computations by hand on scratch paper and copying everything (that is worth keeping) into a spiral-bound notebook. Notebooks being hard to carry, a little bit later I moved on to scanning my notes. Than I decided that my handwriting is quite horrible and I should just TeX everything up instead. Occasionally this has led to me doing my computations directly in LaTeX inside a Vim buffer. Yesterday I discovered a better alternative.

A few of the problems I’ve run into with the various methods of keeping computations include:

1. In deadtree format, the sheets of paper are easily misplaced or soiled, and are not as easily modifiable.
2. Doing a hand computation and then typing up the result takes extra time.
3. Computing directly in LaTeX requires a lot of redundant typing or copying and pasting of parts of the expressions that don’t change from line to line.
4. Typed notes (and sometimes written notes) don’t easily show (unless you take the extra time to format it) the changes between the lines, and sometimes a few months later the computation becomes hard to follow.

The present solution below has

1. Storage in electronic format for easy backup and search.
2. Doing the calculation directly on a computer to avoid the two-step process.
3. Let the computer do the work of copying unchanged expressions for you.
4. Let the computer do the formatting; the computation is written in an almost-human-readable code that is self-documentary.

Software needed
What I discovered, is the IPython Notebook (link to project homepage). The “notebook” part of the name, I believe, refers to its semblance to Mathematica/MatLab/Maxima style notebooks where one sequentially and interactively enter commands and observe their output. Unlike the mentioned software, this notebook is for interactively using the Python programming language. Furthermore, it supports inline documentation using MarkDown and MathJax! (This can be had by changing the cell type to Markdown, instead of the default Python code.)

If you want to see for yourself how this works, you will need to install IPython Notebook yourself. (In Gentoo linux, emerge ipython with the notebook use flag set; in Debian or Ubuntu, you can just apt-get the relevant packages.)

Doing computations
With a little bit of (very messy and unprofessional) Python code, the Notebook can be convinced to print certain outputs prettified by MathJax. Here is the set-up I used:

from IPython.core.display import *

global MathJax
MathJax = True
def MDPL(string): display(Math(string)) if MathJax else display(Latex(string))

class math_expr(object):
'''''Math Expression object'''''

def __init__(self,arg1):
'''''init takes arg: list of atoms, each atom being a compilable chunck of LaTeX expression'''''
self.listofatoms = arg1

def show(self):
'''''Displays the content of the expression in mathmode'''''
MDPL(comp_str(self.listofatoms))

def replace(self,pos,newstr):
'''''Replaces an atom with another atom'''''
MDPL(comp_str(self.colouratoms([pos])))
self.listofatoms[pos] = newstr
MDPL(comp_str(self.colouratoms([pos],True)))

def merge(self,positions):
'''''Merges atoms: the input is a list of positions. The new atom is placed at the position of the foremost of the positions'''''
MDPL(comp_str(self.colouratoms(positions)))
temp = [ self.listofatoms[i] for i in positions ]
positions.sort()
positions.reverse()
for i in positions: del self.listofatoms[i]
self.listofatoms.insert(positions[-1],comp_str(temp))
MDPL(comp_str(self.colouratoms([positions[-1]],True)))

def split(self,pos,newatoms):
'''''Splits atoms: replaces an atom in place with multiple sub atoms'''''
MDPL(comp_str(self.colouratoms([pos])))
del self.listofatoms[pos]
templen = len(newatoms)
while len(newatoms) &gt; 0:
self.listofatoms.insert(pos,newatoms.pop())
MDPL(comp_str(self.colouratoms(range(pos, pos+templen),True)))

def cancel(self,positions):
'''''Cancels a bunch of terms: input a list of positions'''''
MDPL(comp_str(self.colouratoms(positions)))
positions.sort()
positions.reverse()
for i in positions: del self.listofatoms[i]
self.fullshow()

def move(self,posini,posfin):
'''''Move atom at posini to posfin, pushing all others back'''''
MDPL(comp_str(self.colouratoms([posini])))
temp = self.listofatoms.pop(posini)
self.listofatoms.insert(posfin if posfin &lt; posini else posfin-1, temp)
MDPL(comp_str(self.colouratoms([posfin if posfin &lt; posini else posfin-1],True)))

def colouratoms(self,positions,labelled=False):
'''''Returns the list of atoms, but with selected terms coloured'''''
temp = list(self.listofatoms)
if labelled:
self.labelatoms()
temp = list(self.labeledatoms)
for i in positions: temp[i] = &quot;\color{red}{&quot;+temp[i]+&quot;}&quot;
return temp

def labelatoms(self):
self.labeledatoms = [ &quot;\underbrace{&quot; + self.listofatoms[i] + &quot;}_{&quot; + str(i) + &quot;}&quot; for i in range(len(self.listofatoms)) ]

def fullshow(self):
'''''Shows the content whilst labeling positions'''''
self.labelatoms()
MDPL(comp_str(self.labeledatoms))


The entire python code can be copied and saved somewhere on your computer. After launching the Notebook, drag the file and drop it into the first input box. Then hit Shift-Enter to load the code. After that you can do things like:

After loading the preamble in cell 1, we insert a math expression and display it.

and

Next we do some computations (manipulations of the expression).

After I am done, I just click “Save” and the entire computation is saved, and bits and pieces of it can be easily copied out by switching to non-mathJax output.

The code is still in the early stages: a lot more improvements can be made. But it is already a working system.