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
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):
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:
Variable
objects), described by their standard deviation,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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With