Pyepix -- A Wrapper for the ePiX Mathematical Plotting Library

Version 6-1.0.4

Padnos College of Engineering & Computing
Grand Valley State University


Introduction

What is ePiX?

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:

What Does ePiX Produce?

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.

What is Pyepix?

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.

What Does Pyepix Produce?

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.

Getting Started

Requirements

  1. To install ePiX you will need (from the ePiX documentation):
  • 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.)
  1. To install Pyepix you will need:
  • 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.

Installation

  1. Download and install ePiX
  2. Download and install Pyepix. To install, type 'python setup.py install' (you may have to be root to install files to system directories)

Usage

  1. Read through the ePiX tutorial (HTML, also available as PDF)
  2. Read through the Differences between ePiX and Pyepix section below
  3. Look through the sample files in the /usr/lib/python2.3/pyepix/samples directory (substitute /usr/lib/python2.3 with wherever your Python installation resides)

Documentation

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:

  1. 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.
  2. Read the Differences between ePiX and Pyepix section below, as it makes up the difference between ePiX and Pyepix.
  3. 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).

Differences between ePiX and Pyepix

General

  • 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()).

Unsupported Features

  • 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
    

Plotting

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(double f1(double, double), double f2(double, double),
double f3(double, double), P min, P max, mesh coarse, mesh fine)
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.

Plotting Data From Files

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)

Animations

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.