Scientific Computing With Python - Advanced Topics
Scientific Computing With Python - Advanced Topics
Scientific Computing With Python - Advanced Topics
Scientific Computing
with Python
[Advanced Topics]
Eric Jones
[email protected]
Travis Oliphant
[email protected]
Enthought
www.enthought.com
enthought
Topics
Python as Glue
Wrapping Fortran Code
Wrapping C/C++
Parallel Programming
enthought
Python as Glue
enthought
enthought
Electromagnetics Example
(1) Parallel simulation
(2) Create plot
(3) Build HTML page
enthought
Internet
Python
Users
Equipment
New
Algorithm
enthought
enthought
Tools
C/C++ Integration
SWIG
SIP
Pyrex
boost
weave
www.swig.org
www.riverbankcomputing.co.uk/sip/index.php
nz.cosc.canterbury.ac.nz/~greg/python/Pyrex
www.boost.org/libs/python/doc/index.html
www.scipy.org/site_content/weave
FORTRAN Integration
f2py
PyFort
cens.ioc.ee/projects/f2py2e/
pyfortran.sourceforge.net
enthought
f2py
Author: Pearu Peterson at Center for
Nonlinear Studies Tallinn, Estonia
Automagically wraps Fortran 77/90/95
libraries for use in Python. Amazing.
f2py is specifically built to wrap Fortran
functions using NumPy arrays.
enthought
Python
PythonExtension
Extension
Module
Module
fcopy.f
fcopymodule.so
fcopymodule.so
f2py c fcopy.f m fcopy
enthought
>>> a = rand(1000) +
1j*rand(1000)
fcopy(ain,n,aout)
Required arguments:
ain : input rank-1 array('D') with bounds (*)
n : input int
aout : input rank-1 array('D') with bounds (*)
>>> b = zeros((1000,),D)
>>> fcopy.fcopy(a,1000,b)
enthought
More Sophisticated
Fortran
File
Interface
File
fcopy.f
fcopy.pyf
hand edit
Python
PythonExtension
Extension
Module
Module
fcopymodule.so
fcopymodule.so
enthought
More Sophisticated
Interface file fcopy.pyf
!
-*- f90 -*python module fcopy ! in
interface ! in :fcopy
subroutine fcopy(ain,n,aout) ! in :fcopy:fcopy.f
double complex dimension(n), intent(in) :: ain
integer, intent(hide),depend(ain) :: n=len(ain)
double complex dimension(n),intent(out) :: aout
end subroutine fcopy
end interface
end python module fcopy
! This file was auto-generated with f2py (version:2.37.233-1545).
! See http://cens.ioc.ee/projects/f2py2e/
Give f2py
some hints as
to what these
variables are
used for and
how they may
be related in
Python.
>>> a = rand(100,F)
>>> b = fcopy.fcopy(a)
>>> print b.typecode()
D
enthought
Simply Sophisticated
Fortran File
fcopy.f
Python
PythonExtension
Extension
Module
Module
hand edit
fcopymodule.so
fcopymodule.so
f2py c fcopy.f m fcopy
enthought
Simply Sophisticated
Fortran file fcopy2.f
C
SUBROUTINE FCOPY(AIN,N,AOUT)
A few directives can help
C
CF2PY INTENT(IN), AIN
f2py interpret the source.
CF2PY INTENT(OUT), AOUT
CF2PY INTENT(HIDE), DEPEND(A), N=LEN(A)
DOUBLE COMPLEX AIN(*)
INTEGER N
DOUBLE COMPLEX AOUT(*)
DO 20 J = 1, N
>>> import fcopy
AOUT(J) = AIN(J)
>>> info(fcopy.fcopy)
20
CONTINUE
fcopy - Function signature:
END
aout = fcopy(ain)
>>> a = rand(1000)
>>> import fcopy
>>> b = fcopy.fcopy(a)
Required arguments:
ain : input rank-1 array('D') with bounds (n)
Return objects:
aout : rank-1 array('D') with bounds (n)
enthought
compile
*.f
Library
libflib.a
Interface File
flib.pyf
hand edited
C-extension
Module
flibmodule.c
either one
Library of
Fortran Files
f2py alib.pyf
Shared extension
Module
flibmodule.so
f2py c alibmodule.c l alib
enthought
enthought
scipy_distutils
How do I distribute this great new extension module?
Recipient must have f2py and scipy_distutils installed (both
are simple installs)
Create setup.py file
Distribute *.f files with setup.py file.
Optionally distribute *.pyf file if youve spruced up the
interface in a separate interface file.
Supported Compilers
g77, Compaq Fortran, VAST/f90 Fortran, Absoft F77/F90,
Forte (Sun), SGI, Intel, Itanium, NAG, Lahey, PG
enthought
Complete Example
In scipy.stats there is a function written entirely in Python
>>> info(stats.morestats._find_repeats)
_find_repeats(arr)
Find repeats in the array and return a list of the
repeats and how many there were.
enthought
Complete Example
Fortran file futil.f
C
Sorts an array arr(1:N) into
SUBROUTINE DQSORT(N,ARR)
CF2PY INTENT(IN,OUT,COPY), ARR
CF2PY INTENT(HIDE), DEPEND(ARR), N=len(ARR)
INTEGER N,M,NSTACK
REAL*8 ARR(N)
PARAMETER (M=7, NSTACK=100)
INTEGER I,IR,J,JSTACK, K,L, ISTACK(NSTACK)
REAL*8 A,TEMP
END
C
CF2PY
CF2PY
CF2PY
CF2PY
CF2PY
END
enthought
Complete Example
Try It Out!!
>>> from scipy import *
>>> a = stats.randint(1,30,size=1000)
>>> reps, nums = find_repeats(a)
>>> print reps
[
1.
12.
23.
2.
13.
24.
3.
14.
25.
4.
15.
26.
5.
16.
27.
6.
17.
28.
7.
8.
18. 19.
29.]
9.
20.
10.
21.
11.
22.
enthought
Complete Example
Packaged for Individual release
#!/usr/bin/env python
# File: setup_futil.py
from scipy_distutils.core import Extension
ext = Extension(name = 'futil',
sources = ['futil.f'])
if __name__ == "__main__":
from scipy_distutils.core import setup
setup(name = 'futil',
description
= "Utility fortran functions",
author
= "Travis E. Oliphant",
author_email
= "[email protected]",
ext_modules = [ext]
)
# End of setup_futil.py
enthought
Weave
enthought
weave
weave.blitz()
Translation of Numeric array expressions to C/C++ for
fast execution
weave.inline()
Include C/C++ code directly in Python code for on-thefly execution
weave.ext_tools
Classes for building C/C++ extension modules in
Python
enthought
weave.inline
>>> import weave
>>> a=1
>>> weave.inline('std::cout << a << std::endl;',['a'])
sc_f08dc0f70451ecf9a9c9d4d0636de3670.cpp
Creating library <snip>
1
>>> weave.inline('std::cout << a << std::endl;',['a'])
1
>>> a='qwerty'
>>> weave.inline('std::cout << a << std::endl;',['a'])
sc_f08dc0f70451ecf9a9c9d4d0636de3671.cpp
Creating library <snip>
qwerty
>>> weave.inline('std::cout << a << std::endl;',['a'])
qwerty
enthought
enthought
ext_tools example
import string
from weave import ext_tools
def build_ex1():
ext = ext_tools.ext_module('_ex1')
# Type declarations define a sequence and a function
seq = []
func = string.upper
code = """
py::tuple args(1);
py::list result(seq.length());
for(int i = 0; i < seq.length();i++)
{
args[0] = seq[i];
result[i] = PyEval_CallObject(func,py::tuple(args[0]));
}
return_val = result;
"""
func = ext_tools.ext_function('my_map',code,['func','seq'])
ext.add_function(func)
ext.compile()
try:
from _ex1 import *
except ImportError:
build_ex1()
from _ex1 import *
if __name__ == '__main__':
print my_map(string.lower,['asdf','ADFS','ADSD'])
enthought
Efficiency Issues
PSEUDO C FOR STANDARD
NUMERIC EVALUATION
>>> c = a + b + c
>>> c = a + b + c
tmp1
tmp2
// c code
// tmp1 = a + b
tmp1 = malloc(len_a * el_sz);
for(i=0; i < len_a; i++)
tmp1[i] = a[i] + b[i];
// tmp2 = tmp1 + c
tmp2 = malloc(len_c * el_sz);
for(i=0; i < len_c; i++)
tmp2[i] = tmp1[i] + c[i];
// c code
// 1. loops fused
// 2. no memory allocation
for(i=0; i < len_a; i++)
c[i] = a[i] + b[i] + c[i];
enthought
Ex
x t
1
dH y
dH z
2 x
t
t
=
E +
x t dz
x t dy
x t x
x +
x +
1+
2
2
2 x
ca_x[:,1:,1:]
* ex[:,1:,1:]
+ cb_y_x[:,1:,1:] * (hz[:,1:,1:] - hz[:,:-1,:])
- cb_z_x[:,1:,1:] * (hy[:,1:,1:] - hy[:,1:,:-1])
enthought
weave.blitz
weave.blitz compiles array
expressions to C/C++ code using
the Blitz++ library.
WEAVE.BLITZ VERSION OF SAME EQUATION
>>> from scipy import weave
>>> # <instantiate all array variables...>
>>> expr = ex[:,1:,1:] =
ca_x[:,1:,1:]
* ex[:,1:,1:]\
+ cb_y_x[:,1:,1:] * (hz[:,1:,1:] - hz[:,:-1,:])\
- cb_z_x[:,1:,1:] * (hy[:,1:,1:] - hy[:,1:,:-1])
>>> weave.blitz(expr)
< 1. translate expression to blitz++ expression>
< 2. compile with gcc using array variables in local scope>
< 3. load compiled module and execute code>
enthought
weave.blitz benchmarks
Equation
Numeric
(sec)
Inplace
(sec)
compiler
(sec)
Speed
Up
Float (4 bytes)
(512,512)
0.027
0.019
0.024
1.13
a = b + c + d (512x512)
0.060
0.037
0.029
2.06
0.161
0.060
2.68
FDTD
0.890
0.323
2.75
a=b+c
(100x100x100)
Double (8 bytes)
(512,512)
0.128
0.106
0.042
3.05
a = b + c + d (512x512)
0.248
0.210
0.054
4.59
0.631
0.070
9.01
FDTD
3.399
0.395
8.61
a=b+c
(100x100x100)
enthought
u u
+ 2 =0
2
x
y
2
PURE PYTHON
1 Volt
2000 SECONDS
enthought
29.0 SECONDS
10.2 SECONDS
weave.inline(expr,size_check=0)
err = sum((old_u u)**2)
enthought
4.3 SECONDS
code = """
#line 120 "laplace.py" (This is only useful for debugging)
double tmp, err, diff;
err = 0.0;
for (int i=1; i<nx-1; ++i) {
for (int j=1; j<ny-1; ++j) {
tmp = u(i,j);
u(i,j) = ((u(i-1,j) + u(i+1,j))*dy2 +
(u(i,j-1) + u(i,j+1))*dx2)*dnr_inv;
diff = u(i,j) - tmp;
err += diff*diff;
}
}
return_val = sqrt(err);
"""
err = weave.inline(code, ['u','dx2','dy2','dnr_inv','nx','ny'],
type_converters = converters.blitz,
compiler = 'gcc',
extra_compile_args = ['-O3','-malign-double'])
enthought
Laplace Benchmarks
Method
Speed
Up
1897.0
0.02
Numeric
29.0
1.00
weave.blitz
10.2
2.84
weave.inline
4.3
6.74
weave.inline (fast)
2.9
10.00
3.2
9.06
2.4
12.08
Pure Python
Run Time
(sec)
Debian Linux, Pentium III, 450 MHz, Python 2.1, 192 MB RAM
Laplace solve for 500x500 grid and 100 iterations
Speed-up taken as compared to Numeric
enthought
SWIG
enthought
SWIG
Author: David Beazley at Univ. of Chicago
Automatically wraps C/C++ libraries for use in
Python. Amazing.
SWIG uses interface files to describe library
functions
No need to modify original library code
Flexible approach allowing both simple and complex
library interfaces
Well Documented
enthought
SWIG Process
Interface
File
C Extension
File
SWIG
lib.i
lib_wrap.c
Writing this is
your responsibility (kinda)
compile
Library Files
*.h files *.c files
compile
Python
PythonExtension
Extension
Module
Module
libmodule.so
libmodule.so
enthought
Simple Example
fact.h
example.i
#ifndef FACT_H
#define FACT_H
#endif
fact.c
#include fact.h
int fact(int n)
{
if (n <=1) return 1;
else return n*fact(n-1);
}
enthought
\
\
enthought
enthought
SWIG Example 2
vect.h
int* vect(int x,int y,int z);
int sum(int* vector);
vect.c
#include <malloc.h>
#include vect.h
int* vect(int x,int y, int z){
int* res;
res = malloc(3*sizeof(int));
res[0]=x;res[1]=y;res[2]=z;
return res;
}
int sum(int* v) {
return v[0]+v[1]+v[2];
}
example2.i
Identical to example.i if you replace
fact with vect.
TEST IN PYTHON
>>> from example2 import *
>>> a = vect(1,2,3)
>>> sum(a)
6 #works fine!
# Lets take a look at the
# integer array a.
>>> a
'_813d880_p_int'
# WHAT THE HECK IS THIS???
enthought
enthought
Typemaps
example_wrap.c
static PyObject *_wrap_sum(PyObject *self, PyObject *args) {
...
if(!PyArg_ParseTuple(args,"O:sum",&arg0))
return NULL;
...
result = (int )sum(arg0);
...
return resultobj;
}
enthought
Typemaps
The result? Standard C pointers are mapped to
NumPy arrays for easy manipulation in Python.
YET ANOTHER EXAMPLE NOW WITH TYPEMAPS
>>> import example3
>>> a = example3.vect(1,2,3)
>>> a
# a should be an array now.
array([1, 2, 3], 'i') # It is!
>>> example3.sum(a)
6
The typemaps used for example3 are included in the handouts.
Another example that wraps a more complicated C function used in
the previous VQ benchmarks is also provided. It offers more generic
handling 1D and 2D arrays.
enthought
enthought
enthought
enthought
Threads
Python threads are built on POSIX and
Windows threads (hooray!)
Python threads share a lock that
prevents threads from invalid sharing
Threads pass control to another thread
every few instructions
during blocking I/O (if properly guarded)
when threads die
enthought
enthought
Making a thread
we will work at the prompt!
>>> from threading import *
>>> def f(): print hello
>>> T = Thread(target=f)
>>> T.start()
enthought
Thread operations
currentThread()
T.start()
T.join()
T.getName() / T.setName()
T.isAlive()
T.isDaemon() / T.setDaemon()
enthought
enthought
Subclassing a thread
from threading import *
class myThread(Thread):
def __init__(self,x,**kw):
Thread.__init__(self,**kw) #FIRST!
self.x = x
def run():
print self.getName()
print I am running,self.x
T = myThread(100)
T.start()
enthought
CAUTION!
Threads are really co-routines!
Only one thread can operate on Python
objects at a time
Internally, threads are switched
If you write extensions that are intended
for threading, use
PY_BEGIN_ALLOW_THREADS
PY_END_ALLOW_THREADS
enthought
cow
enthought
Electromagnetic Scattering
Airborne Radar
RCS (dB)
-5
-10
-15
-20
-25
100
Inputs
environment, target
mesh, and
multiple frequencies
Mem: KB to Mbytes
SMALL
Computation
N3 CPU
N2 storage
Time: a few seconds
to days
Mem: MB to GBytes
LARGE!
150
200
250
300
Frequency (MHz)
350
Outputs
Radar Cross Section
values
Mem: KB to MBytes
SMALL
400
enthought
cow.py
Master
Python
>>>
Socket
Node 0
Node 1
Node 2
Python
>>>
Python
>>>
Python
>>>
58
enthought
Cluster Creation
Master
>>> import scipy.cow
#
[name, port]
>>> machines = [['s0',11500],['s1',11500],['s2',11500]]
>>> cluster = scipy.cow.machine_cluster(machines)
>>>
enthought
start an interpreter
listening on port 11500
on each remote machine
s0
s1
s2
Python
>>>
Python
>>>
Python
>>>
enthought
s0
Python
>>> a = 1
s1
Python
>>> a = 1
s2
Python
>>> a = 1
enthought
s0
Python
>>> a = 1
>>> a
1
s1
Python
>>> a = 1
>>> a
1
s2
Python
>>> a = 1
>>> a
1
enthought
cluster.apply()
Master
# run a function on each remote machine
>>> import os
>>> cluster.apply(os.getpid)
(123,425,947)
s0
>>> os.getpid()
123
s1
>>> os.getid()
425
s2
>>> os.getpid()
947
enthought
cluster.exec_code()
Master
# run a code fragment on each remote machine
>>> cluster.exec_code('import os; pid = os.getpid()',
...
returns = ('pid',))
(123,425,947)
s0
>>> import os
>>> os.getpid()
123
s1
>>> import os
>>> os.getpid()
425
s2
>>> import os
>>> os.getpid()
947
enthought
cluster.loop_apply()
Master
# divide task evenly (as possible) between workers
>>> import string
>>> s = ['aa','bb','cc','dd']
>>> cluster.loop_apply(string.upper, loop_var=0, args=(s,) )
('AA','BB','CC','DD')
s0
>>> x=upper('aa')
>>> y=upper('bb')
>>> (x,y)
('AA','BB')
s1
>>> x=upper('cc')
>>> (x,)
('CC',)
s2
>>> x=upper('dd')
>>> (x,)
('DD',)
enthought
enthought
enthought
info()
Display information about each worker node including its name,
processor count and type, total and free memory, and current work
load.
enthought
Query Operations
>>> herd.cluster.info()
MACHINE
CPU
GHZ
s0
2xP3
0.5
s1
2xP3
0.5
s2
2xP3
0.5
MB TOTAL
960.0
960.0
960.0
MB FREE
930.0
41.0
221.0
>>> herd.cluster.ps(user='ej',cpu='>50')
MACHINE USER PID %CPU %MEM TOTAL MB RES MB
s0
ej
123 99.9
0.4
3.836
3.836
s1
ej
425 99.9
0.4
3.832
3.832
s2
ej
947 99.9
0.4
3.832
3.832
LOAD
0.00
1.00
0.99
CMD
python...
python...
python...
enthought
enthought
CPUs
Run Time
(sec)
Speed
Up
Efficiency
(1) standard
2.97
(2) loop_apply
11.91
0.25
-400%
(3) scatter/compute/gather
13.83
0.21
-500%
Test Setup:
The array a is 8192 by 512. ffts are applied to each row
independently as is the default behavior of the FFT module.
The cluster consists of 16 dual Pentium II 450 MHz machines
connected using 100 Mbit ethernet.
enthought
CPUs
Run Time
(sec)
Speed
Up
Efficiency
(1) standard
2.97
(2) loop_apply
11.91
0.25
-400%
(3) scatter/compute/gather
13.83
0.21
-500%
1.49
2.00
100%
0.76
3.91
98%
16
0.24
12.38
78%
32
0.17
17.26
54%
Moral:
If data can be distributed among the machines once and then
manipulated in place, reasonable speed-ups are achieved.
enthought
Electromagnetics
EM Scattering Problem
CPUs
Run Time
(sec)
Speed
Up
Efficiency
32
8.19
31.40
98.0%
Land Mine
64 freqs, 1152 edges
32
285.12
31.96
99.9%
enthought
enthought
pyMPI
enthought
0
1
2
3
enthought
Broadcasting Data
import mpi
import math
if mpi.rank == 0:
data = [sin(x) for x in range(0,10)]
else:
data = None
common_data = mpi.bcast(data)
enthought
mpi.bcast()
bcast() broadcasts a value from the
root process (default is 0) to all other
processes
bcasts arguments include the message
to send and optionally the root sender
the message argument is ignored on all
processors except the root
enthought
Scattering an Array
# You can give a little bit to everyone
import mpi
from math import sin,pi
if mpi.rank == 0:
array = [sin(x*pi/99) for x in
range(100)]
else:
array = None
# give everyone some of the array
local_array = mpi.scatter(array)
enthought
mpi.scatter()
scatter() splits an array, list, or tuple
evenly (roughly) across all processors
the function result is always a [list]
an optional argument can change the
root from rank 0
the message argument is ignored on all
processors except the root
enthought
enthought
mpi.gather() / mpi.allgather()
gather appends lists or tuples into a
master list on the root process
if you want it on all ranks, use
mpi.allgather() instead
every rank must call the gather()
enthought
Reductions
# You can bring data together in interesting ways
import mpi
x_cubed = mpi.rank**3
sum_x_cubed = mpi.reduce(x_cubed,mpi.SUM)
enthought
mpi.reduce() / mpi.allreduce()
The reduce (and allreduce) functions
apply an operator across data from all
participating processes
You can use predefined functions
mpi.SUM, mpi.MIN, mpi.MAX, etc
enthought
enthought
enthought
VTK Gallery
enthought
VTK Pipeline
PIPELINE
OUTPUT
enthought
Cone Example
SETUP
# VTK lives in two modules
from vtk import *
# Create a renderer
renderer = vtkRenderer()
# Create render window and connect the renderer.
render_window = vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetSize(300,300)
# Create Tkinter based interactor and connect render window.
# The interactor handles mouse interaction.
interactor = vtkRenderWindowInteractor()
interactor.SetRenderWindow(render_window)
enthought
enthought
enthought
Mesh Generation
POINTS AND CELLS
3
2
0
id
0
1
2
3
points
x y z temp
0 0 0 10
1 0 0 20
0 1 0 20
0 0 1 30
triangles
id x y z
0
0 1 3
1
0 3 2
2
1 2 3
3
0 2 1
enthought
Mesh Generation
POINTS AND CELLS
# Create a mesh from these lists
mesh = vtkPolyData()
mesh.SetPoints(verts)
mesh.SetPolys(polygons)
mesh.GetPointData().SetScalars( \
...
temperature)
# Create mapper for mesh
mapper = vtkPolyDataMapper()
mapper.SetInput(mesh)
# If range isnt set, colors are
# not plotted.
mapper.SetScalarRange( \
...
temperature.GetRange())
Code for temperature bar not shown.
enthought
VTK Demo