In the polymake terminology, a client is a subroutine written in C++. There are many
situations where the implementation in a client form is better a perl subroutine,
especially when:
the algorithm needs sophisticated data structures not available in perl
the efficiency and computation time are crucial issues
your programming skills in C++ are much better than in perl
For technical reasons, the current polymake version still requires that a client runs as a separate program. In the next releases
they become real subroutines, and the elements of the C++ API concerning the inter-process communication will disappear. Then you
will have to change the clients developed so far. To minimize the conversion work, we suggest a special structure for client
programs, where the computational part is clearly separated from the communication part. All clients included in the distribution
are already built according to this scheme. You are welcome to use them as learning examples or just as copy-and-paste mines.
Let's look at one of the standard clients, pvolume, which takes the triangulation of
a polytope and calculates its volume. The algorithm is simple enough to let us concentrate on the coding principles.
/* Copyright (c) 1997-2004
...
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License
...
*/
To warm up, we start with legal issues. The polymake system as whole and the C++ API library
in particular are distributed under the conditions of GPL. Since your client is using the
library, it automatically inherits the license, until you have our explicit permittance to change
it. We don't object in general to the commercial use of polymake or its parts, but, for
obvious reasons, we want to be informed about it.
Array.h and linalg.h come from
the Polymake Template Library and define the data structures and
linear algebra functions used in this client. Some classes (like Matrix and Set) are already included
in linalg.h, thus only Array needs a separate #include statement.
namespace polymake { namespace polytope {
All clients have to reside in an application-specific namespace. The namespace has always the same name
as the application itself. The outer namespace polymake contains the definitions of the most
heavily used PTL classes which are common to all applications. This naming convention almost
completely eliminates the needs for explicit qualification of names and using statemens.
This is the main computational function in this client. p is the object (presumably of type
RationalPolytope) whose volume is to be computed. In C++ all polymake object type are
represented by the same class Poly.
Here we see the two most important polymake API methods in action. The method give()
returns the value of a property, in this case it is a boolean BOUNDED.
The method take() creates a new property VOLUME. In this code fragment
we detect the case of an unbounded polyhedron. Its volume is not defined, thus we assign the
special value Poly::undefined to the result property.
Now we proceed with the case of a bounded polytope, where we have to do real computations. Thus we read the necessary properties
using the same method give as above, but now with property names passed in variables.
A remark on the choice of the data structures. Although the most clients keep the
point coordinates kept in a Matrix<Rational> (or in seldom cases Matrix<double>)
and the triangulation in Array< Set<int> >, it is not the only possible choice.
You may perfectly go with std::vector< std::set<int> > and some matrix class of
your own. The main decision criteria must be the run-time characteristics of the class required
for the algorithm, like random access, dynamic reallocation, or efficient set-theoretic
operations.
The only requirements imposed by the polymake API on the data types used for keeping the object
properties are:
the chosen type must be structurally equivalent to the property type; in our example these
are matrix and array<set >
the logical structure of a complex type must be known to the GenericIO layer"
Now that we have gathered all necessary data, we can make the calculations. For each simplex in
Triangulation, we take the coordinates of its vertices, compute the determinant, and sum it up
ignoring the orientation. Finally, we divide by the factorial of the dimension, which is one
less than the coordinate vector dimension due to the homogeneous coordinates used in the application polytope.
The last action ist then to store the computed value in the object's property VOLUME.
Again, this is done by take().
Note the special end-sensitive iterator s created by the function entire
at the beginning of the loop.
It is a PTL's replacement for the standard pair of methods begin() and end().
Here is the end of the substantial part of the client. The rest deals with communication start-up
and error handling; as already said in the introduction, it is the part that will substantially change,
or even disappear, in the next releases.
using namespace polymake;
int main(int argc, const char *argv[])
{
if (argc!=4) {
cerr << "usage: " << argv[0] << " <file> <points_property> <triangulation_property>\n";
return 1;
}
By convention all polymake clients print a short usage message if called with wrong arguments
(especially without arguments.) Even if this client is almost always called by the polymake server from the
production rules and not from the command line, we don't want to break this good tradition.
The non-zero exit code is important for polymake script to detect a client failure.
First, the object p is attached to the polymake object of interest, which is either a data
file (when called directly from the command line) or a "secret code" generated by the polymake
script for a Poly::Object reference (when called from perl via the Modules::client
function.) The set of flags ios::in | ios::out signals that the client is going to
both get properties from the object and create new properties.
Then we call the function polymake::polytope::volume defined above.
All errors occuring in clients are signaled by exceptions. Since there is no way to propagate an
exception between processes (recall, the client is an independent program!) we must catch all
possible errors here, print a descriptive message, and signal the failure by the non-zero exit
code. To say it again, the next releases will deploy a more elegant error handling.
That's it. We recommend to study the source code of few other clients, for example cube
as a typical client creating an object from scratch, or pyramid as a client constructing an object from others.
Useful reference material are the descriptions of the main Template Library classes and
the polymake communication API.