7

How can I watch which functions get called and all variable values in a Python script?

I have just discovered the uncertainties package for Python, and I want to actually figure out how it works so I can explain it to my boss.

Basically, I can't see how the uncertainties are actually calculated. This is probably because I don't know how Python works yet.

For starters, how can I see how c is calculated?

import uncertainties
from uncertainties import ufloat

a = ufloat(1,3)
b = ufloat(2,4)

c = a + b  # How does this work??

print c
Eric O. Lebigot
  • 91,433
  • 48
  • 218
  • 260
masher
  • 3,814
  • 4
  • 31
  • 35
  • 1
    I find google is usually more expedient than a debugger. Have a look at the documentation. Or, at the worst, just diving into the source, if you for example knew that objects can specify methods like `__add__` to implement behavior with arithmetic operators. – roippi Aug 16 '13 at 00:21
  • @roippi The Python documentation I've come across is very different to the Javadocs I am used to and isn't all that intuitive to follow, and I also don't know how to build it (or if it exists) for the uncertainties package. I know that objects can write their own `__add__` function , except that the way I think they get added by reflection, going by the comments, so trying to track everything through the source code is a little tricky. – masher Aug 16 '13 at 00:44
  • 1
    Have you seen the [technical guide](http://pythonhosted.org/uncertainties/tech_guide.html)? – Paulo Almeida Aug 16 '13 at 00:58
  • @PauloAlmeida Yes. I've read the guide, and while it says what is going on, it doesn't say how it is doing it. I know how to calculate the errors, but I'd like to be able to reproduce each step that is done in this package to understand what is actually being done. – masher Aug 16 '13 at 01:07
  • Side note: no need to have `import uncertainties` if you only use `ufloat`. – Eric O. Lebigot Aug 17 '13 at 13:11

3 Answers3

1

If you want to see how things are calculated, pdb is indeed one solution.

However, with a debugger like pdb, it is hard to get a high-level overview of what is going on. This is why reading the code is also useful. For example, if you want to know what a+b does, you can check if type(a).__add__ exists, because if it does, it handles the addition. This is the case with the uncertainties package.

That said, __add__ is indeed implemented in uncertainties through a general mechanism instead of being specifically coded for, so I can tell you the idea behind its implementation, since this is what you seem to be ultimately looking for.

In your example, a and b are Variable objects:

>>> from uncertainties import ufloat                                            
>>> a = ufloat(1, 3)
>>> b = ufloat(2, 4)
>>> type(a)
<class 'uncertainties.Variable'>

Then c = a + b is actually a linear function of a and b, represented by its derivatives with respect to a and b:

>>> c = a + b
>>> type(c)
<class 'uncertainties.AffineScalarFunc'>
>>> c.derivatives
{1.0+/-3.0: 1.0, 2.0+/-4.0: 1.0}

If you know the derivatives of a function with respect to its variables, you can easily get an approximation of its standard deviation from the standard deviations of its variables.

Thus, the main ideas behind the implementation of the uncertainties package is that values are either:

  • random variables like x = 3.14±0.0.1 and y = 0±0.01 (Variable objects), described by their standard deviation,
  • or linear approximations of functions (AffineScalarFunc objects: "affine" because they are linear, "scalar" because their values are real, and "func" because they are functions).

To take a more complicated example, z = 2*x+sin(y) is approximated in (x, y) = (3.14, 0) as 2*x + y. In the implementation, since the approximation is linear, only the derivatives with respect to the variables are stored:

>>> x = ufloat(3.14, 0.01)
>>> y = ufloat(0, 0.01)
>>> from uncertainties.umath import sin
>>> z = 2*x + sin(y)
>>> type(z)
<class 'uncertainties.AffineScalarFunc'>
>>> z.derivatives
{3.14+/-0.01: 2.0, 0.0+/-0.01: 1.0}

The main work done by the uncertainties package is thus to calculate the derivatives of any function involving variables. This is done by the efficient method of automatic differentiation. Concretely, when you do something like a+b, Python automatically calls the Variable.__add__() method, which creates a new linear function by calculating the derivatives of a+b with respect to its variables (the derivatives are both one, because the derivative of a with respect to a is one, and the same for b). More generally, one adds functions, not pure variables: the derivatives of f(a,b) + g(a,b) with respect to a and b is calculated with the chain rule. This is how automatic differentiation works, and this is what is implemented in the uncertainties package. The key function here is uncertainties.wrap(). It is the largest and most complicated function of the whole package, but the code is largely commented, and details on the method are available.

Derivatives then give you the standard deviation of the final function as a function of the standard deviations of the variables (the code of AffineScalarFunc.std_dev() is very simple: the more difficult task is to automatically calculate derivatives).

Eric O. Lebigot
  • 91,433
  • 48
  • 218
  • 260
1

Ignoring the specific case of the uncertainties module, Python provides the sys.settrace function, which can be used to implement things like the Smiliey application tracer

For example, from the docs:

In one terminal window, run the monitor command:

$ smiley monitor

In a second terminal window, use smiley to run an application. This example uses test.py from the test_app directory in the smiley source tree.

$ smiley run ./test.py
args: ['./test.py']
input = 10
Leaving c() [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Leaving b()
Leaving a()

The monitor session will show the execution path and local variables for the app.

Starting new run: ./test.py
test.py:   1: import test_funcs
test.py:   1: import test_funcs
test_funcs.py:   1: import sys
test_funcs.py:   1: import sys
test_funcs.py:   3: def gen(m):
test_funcs.py:   8: def c(input):
dbr
  • 165,801
  • 69
  • 278
  • 343
0

You might take a look at the Python debugger: http://docs.python.org/2/library/pdb.html

As you may know, a debugger is designed to let you watch the parts of the program move during execution.

You might also want to take a look at the source code of the Uncertainties package at https://github.com/lebigot/uncertainties/

andyg0808
  • 1,367
  • 8
  • 18
  • I've got the source; it's a bit of a slog to read through. I'm also investigating pdb now... – masher Aug 16 '13 at 00:48
  • FYI, pdb was basically a LMGTFY on the need for a debugger. I don't know that it's the best debugger out there; I'm not a Python programmer myself. http://stackoverflow.com/questions/1623039/python-debugging-tips (which I came across from Google) might also be of interest. – andyg0808 Aug 16 '13 at 01:03