Version 6-1.0.4
ePiX is a C++ library (written by Andrew D. Hwang) for constructing high-quality mathematically-accurate plots suitable for inclusion in textbooks and journal articles. Here are some examples of the types of plots it can create:
The output of ePiX is an EEPIC file intended for inclusion in a LaTeX document. Even if you do not use LaTeX, you can convert the EEPIC files into EPS (Encapsulated PostScript) files, then into any other format you choose (such as JPEG, PNG, etc.) To do so, however, requires a working LaTeX installation so that the EEPIC files may be translated into EPS. Modern Unix/Linux distributions include LaTeX. Users of other operating systems will have to do some research to determine how/if LaTeX can be installed on their systems.
Pyepix is a Python wrapper for the ePiX C++ library. The benefits of developing your mathematical plots in Pyepix are:
You do not have to learn C++ or fight with the C++ language (assuming that you are not proficient with C++)
You have access to Python's extensive built-in libraries and extension modules.
For example, the Numerical Python and/or numarray extensions provide very useful data analysis and manipulation functions that can be easily integrated with Pyepix.
As another example, the integration of Python CGI code with a web server can be used to construct and display mathematical plots on-the-fly in response to user interaction.
Pyepix produces EEPIC files just like ePiX, because in fact it is just a wrapper for ePiX. You will still need a working LaTeX installation to do anything useful with the EEPIC files.
- The GNU shell, bash
- A C++ compiler and development libraries, preferably g++
- GhostScript, ImageMagick, and teTeX (specifically, the programs convert, dvips, latex, and ps2epsi)
- GNU binutils, textutils, and fileutils (ar, sed, grep, ln, rm, etc.)
- Python 2.3 or later. This version of Pyepix was developed with Python 2.3.4.
- A C++ compiler and development libraries, preferably g++
- GNU make (gmake)
- Optionally, the Python Docutils if you want to rebuild this HTML file from its reStructuredText source.
This file is currently the only documentation for Pyepix. Since Pyepix is a wrapper, most of the interesting documentation is in the library being wrapped: ePiX. To become proficient with Pyepix:
- Read the ePiX tutorial to understand what this is all about. The tutorial serves as the main documentation for writing Python files that use the Pyepix package, even though the tutorial uses C++ code.
- Read the Differences between ePiX and Pyepix section below, as it makes up the difference between ePiX and Pyepix.
- Skim through the sample plot files written in Python, as they illustrate the usage of the library. If you're interested, compare the Pyepix sample files and the sample files from the ePiX distribution and ePiX web site (the Pyepix files were converted by hand from C++ to Python, with not much effort).
The picture size variables in ePiX are functions in Pyepix:
x_min ==> x_min() x_max ==> x_max() y_min ==> y_min() y_max ==> y_max() x_size ==> x_size() y_size ==> y_size()
The Triple class is implemented and accepted anywhere an ePiX point ("P") is. For example:
line(P(0,1), P(3,5)) // In C++ epix.line( (0,1), (3,5) ) # In Python epix.line( P(0,1),P(3,5) )
Both are equally valid, although the tuples have less overhead and will be faster. The benefit of using the P(x,y,z) object is that it has dot-product, Kronecker product, etc. semantics defined, as do points in ePiX. Python tuples do not. Have a look at epix/epix.py for more details on the implementation.
Note especially that class P is a factory function that returns a Triple object, regardless of whether it is called with a 2-tuple or 3-tuple. There are lots of ways of constructing points. The following are equivalent:
P(0,1) # Initialized from components, calls Triple(0,1,0) Triple(0,1) # Calling the class initializer directly with a 2-tuple Triple(0,1,-3) # Calling the class initializer directly with a 3-tuple P( (0,1) ) # Initialized from 2-tuple P( (0,1,-3) ) # Initialized from 3-tuple P( P(0,1) ) # Initialized from another Triple object P( 0+1j ) # Initialized from complex number...equivalent to P(0,1)
It is up to you to decide how to trade off the expressive power of pairs and triples against performance.
The various constants (like CIRC, and rt (label position)) defined by ePiX are available as constants in the Python module. For example:
import epix epix.marker( (1,-1), epix.OPLUS ) epix.label( (4,0), (2,4), "t", epix.tr )
or:
from epix import * marker( (1,-1), OPLUS ) label( (4,0), (2,4), "t", tr )
ePiX functions that take function parameters (like plot()) take Python callable function parameters in Pyepix. For example:
def f(x): return x*x plot(f, -5, 5, 20)
See just about any sample file in the samples/Images directory for more examples of using callable functions as arguments to plot commands.
When a C function is expected to return a triple, the Python function must return something that looks like a triple: a 2-tuple, a Triple object, etc. As mentioned above, staying away from formal Triple objects greatly improves speed.
Otherwise, Python functions are expected to return floating-point scalars.
ePiX functions which take function parameters (like plot()) are not thread safe. In a multi-threaded Python program, you must ensure that no more than one such ePiX function is active at the same time.
There are various ways around this (define a global lock, implement thread-local-storage) but none of this seems to be worth it given the intended uses of ePiX. If you really really have a valid use for Pyepix in a threaded environment, let me know!
Some functions formally take an integer as an argument, but Pyepix allows them to take a float so that, for example, the common idiom:
h_axis(P(x_min(), 0), P(x_max(), 0), 2*x_size())
works correctly. In this example, x_size() is a floating-point value but the last parameter of h_axis() is an integer.
Internally, Pyepix rounds the last parameter to the nearest integer before calling the ePiX library function.
The only functions that are NOT float-tolerant are those which require an integer to indicate some constant, like CIRC (essentially, enumerations), or for which non-integers just don't make sense (like gcd()).
The contributed module (Cartesian co-ordinates) is not yet supported.
The ePiX id() function is not implemented as it conflicts with Python's built-in id() function. If you don't need the latter and you still want to use the former, you can simply define your own id() function:
def id(x): return x
Python doesn't know a function's return type ahead of time, nor is it easy to figure out how many function parameters are expected. For this reason, different function names are used to represent overloaded functions in ePiX.
For example, the ePiX plot() function can either be called (in C++) as:
plot(f1, f2, f3, ...)
or:
plot(Triple f, ...)
where f returns a triple. In Pyepix there are two separate functions:
plot(f1, f2, f3, ...) plot_triple(f, ...)
The new function plot_triple() is used to indicate that the Python function f() returns a length-3 sequence instead of a simple floating-point number.
The table below maps ePiX function signatures to equivalent functions in Python. If an ePiX plotting function is not listed in the table, it means that the Pyepix function has the same name with no variations.
| ePiX Function Signature | Pyepix Function | Notes |
|---|---|---|
| plot(double f(double), t_min, t_max, num_pts) | plot | |
| plot(double f1(double), double f2(double), t_min, t_max, num_pts) | plot | |
| plot(double f1(double), double f2(double), double f3(double), t_min, t_max, num_pts) | plot | |
| plot(char* filename, markType, columns, col1, col2, col3, P f(double, double, double)) | plot | |
| plot(FILEDATA& data_columns, markType, col1, col2, col3, P f(double, double, double)) | plot | (1) |
| plot(P f(double), t_min, t_max, num_pts) | plot_triple | |
| plot(double f(double, double), P p1, P p2, mesh coarse, mesh fine) | plot_surf | |
|
plot_surf | |
| plot(double f(double, double), domain R) | plot_surf | |
| plot(P f(double, double), P p1, P p2, mesh coarse, mesh fine) | plot_surf_triple | |
| plot(P f(double, double), domain R) | plot_surf_triple | |
| plot(P f(P), t_min, t_max, num_pts) | plot_triple_triple | |
| plot(P f(P), domain R) | plot_triple_triple | |
| tan_line(double f1(double), double f2(double), t0) | tan_line | |
| tan_line(double f(double), t0) | tan_line | |
| tan_line(P f(double), t0) | tan_line_triple | |
| envelope(double f1(double), double f2(double), double, double, int) | envelope | |
| envelope(double f(double), double, double, int) | envelope | |
| envelope(P f(double), double, double, int) | envelope_triple | |
| tan_field(double f1(double), double f2(double), double, double, double) | tan_field | |
| tan_field(P f(double), double, double, double) | tan_field_triple | |
| slope_field(P F(double, double), p, q, n1, n2) | slope_field | |
| slope_field(P F(double, double, double), p, q, n1, n2) | slope_field_3d | |
| dart_field(P F(double, double), p, q, n1, n2) | dart_field | |
| dart_field(P F(double, double, double), p, q, n1, n2) | dart_field_3d | |
| vector_field(P F(double, double), p, q, n1, n2) | vector_field | |
| vector_field(P F(double, double, double), p, q, n1, n2) | vector_field_3d | |
| ode_plot(P F(double, double), start, t_max, num_pts) | ode_plot | |
| ode_plot(P F(double, double, double), start, t_max, num_pts) | ode_plot_3d | |
| ode_plot(P F(double, double), start, t_min, t_max, num_pts) | ode_plot | |
| ode_plot(P F(double, double, double), start, t_min, t_max, num_pts) | ode_plot_3d | |
| flow(P F(double, double), start, t_max, num_pts) | flow | |
| flow(P F(double, double, double), start, t_max, num_pts) | flow_3d |
(1) See the Plotting Data From Files section below for information on using the FILEDATA class.
The Pyepix plot function can take a string argument as its first parameter representing a filename containing multi-column plot data. This behavior is the same as that of ePiX. Consult the ePiX tutorial for usage information.
Several ePiX plotting functions use a FILEDATA structure to represent multi-column data. This C++ type is essentially a vector of vectors. In Pyepix, the FILEDATA structure is replaced by a sequence of sequences (or tuple of tuples, etc.) The top-level sequence represents the columns, and each element (which is itself a sequence) represents all of the data points in that column. For example, to plot the function f(x) = 1/x from its data points (rather than as a mathematical function) we can say:
X = [] Y = [] for i in range(1,11): X.append(i) Y.append(1.0/i) filedata = [X, Y] plot(filedata, PATH, 1, 2, 0)
Other ePiX functions that use FILEDATA structures (like avg and histogram) work in the same way. The ePiX read function has a different syntax in Pyepix. It takes a filename string argument and returns the sequence-of-sequences structure as its return value:
filedata = read(filename)
With ePiX, animations are created by using the flix script. This script calls the executable file created by compiling a *.xp file with two parameters: the current frame number and the total number of frames. For example, a source file named atest.xp would be animated using:
flix atest.xp
Within the flix script, the executable would be called several times with increasing frame numbers:
atest 0 23 atest 1 23 atest 2 23 ...
This process generates a PNG file for each frame of the animation, then uses the ImageMagick convert program to stitch the frames together into a single MNG animation file. The *.xp file typically divides the current frame by the number of frames and assigns this ratio to the global variable tix. The code in the *.xp file uses the value of tix to parameterize the plot.
In Pyepix, the process is very similar. A source file should parse its first two command-line parameters and assign their ratio to the global variable tix:
import sys if len(sys.argv) == 3: temp1 = float(sys.argv[1]) temp2 = float(sys.argv[2]) tix=temp1/temp2 else: tix = 0.0
Instead of using the flix script, the pyflix script (distributed with Pyepix) should be used. This script is essentially the flix script modified to recognize *.py files and to call them directly rather than trying to compile them. For example:
pyflix atest.py
The script goes through the same steps, calling the *.py file with the frame parameters, creating PNG files, then stitching the PNG files together into an MNG file. Type pyflix --verbose --help for a listing of available script options.
For some working examples, see the samples in the samples/Animations directory.