Table of Contents

Writing C++ Clients

In polymake speak, a client is a function written in C++, devoted to a strictly confined task, like constructing a new polytope of some special kind, or performing some performance-critical operation like convex hull computation. Clients always belong to an application, the source code sitting in apps/APPNAME/src directory of the core system or of an extension.

Overall Structure

#include "polymake/client.h"
#include "polymake/Matrix.h"
#include "polymake/Rational.h"
#include "polymake/list"
...
namespace polymake { namespace APPNAME {

function definitions

glue declarations

} }

polymake/client.h is a mandatory header included in each client. It is followed by further headers, including, but not restricted to, the polymake template library, standard C++ library, and any involved third-party software. For the most popular STL container classes polymake offers small adornments (in form of traits classes) allowing for sharing the data between C++ and perl as well as formatted screen output; in this cases, a header file with polymake/ prefix should be included.

The namespaces are obligatory too; the name of the inner namespace must exactly match the application the client belongs to.

Glue declarations are special macros described below; their purpose is to make the perl part of polymake aware of the existence of the C++ function and to provide for conversion of arguments and results between the C++ and perl worlds.

Auxiliary functions without direct connection to perl should be enclosed in an anonymous namespace nested within application namespace, to prevent potential clashes with other clients.

If you have the feeling that some auxiliary functions may be useful in several clients, you should declare them in a separate header file, residing in the application subtree apps/APPNAME/include, and include this header in all clients using it. Which client source file will harbor their definitions, doesn't matter from the technical point of view; choose it upon your taste.

Object Interface

Under Objects in this section the “big” polymake objects are meant, like Polytope or SimplicialComplex. They are represented by C++ class BigObject, which is automatically defined if you include polymake/client.h. The interface of BigObject looks practically identical to that of its perl brother Object, up to inevitable syntactical deviations.

Wherever a string literal appears as a function argument in the examples below, a std::string instance may be used as well.

Construction

  1. Creating an empty object:

    BigObject p("Polytope<Rational>");

    with a type given literally

    BigObject p("Polytope", mlist<CoordType>());

    with a parametrized type depending on template parameters of the enclosing function or class

    BigObject p(q.type());

    with a same type as of another BigObject

    BigObject p(t);

    with a type given by an ObjectType instance

    An empty object may be filled with properties one by one, using take methods described further. The initialization phase is completed as soon as one of the following happens:

    • give method is called, asking for a property not contained in the initial set
    • the object is returned by the C++ client back to its caller on the perl side

    After this, the object becomes immutable, that is, only properties declared as mutable may be changed and/or added.

  2. Creating an object in an invalid state:

    BigObject p{};

    The default constructor is primarily provided to enable arrays of Objects. The only allowed operations on an Object in invalid state is an assignment from another BigObject.

  3. Creating a copy of another object:

    BigObject p=q.copy();

    as an exact copy less temporary properties

    BigObject p("Polytope<Float>", q);

    as a copy of the same or related type specified literally

    BigObject p(t, q);

    … or given by an ObjectType instance

    The object created this way is immutable from the very beginning.

    The source and target types in the converting copy constructor are called related if either one of them is derived from another, or both stem from the same parametrized type. If the target type is the ancestor of the source type, only properties being common to both types are copied, the rest is silently discarded. The same applies for augmented subobject types: only properties defined for the stand-alone object type are copied. Copying from one parametrized instance to another implies conversion of all properties whose types are dependent on the parameters being different between the target and the source.

    For example, let p be a Polytope<Rational>:

    • BigObject q("Polytope<Float>", p) will convert VERTICES, FACETS, and all other coordinate-related properties from Matrix<Rational> into Matrix<Float>, Vector<Rational> into Vector<Float>, etc.
    • BigObject g=p.give("GRAPH");
      BigObject q=g.copy(); won't copy EDGE_DIRECTIONS because this property is only defined for a graph of a polytope but not for a stand-alone graph object.
    • BigObject q("PointConfiguration", p); is invalid, because the source and target types are unrelated.

Attention: the standard copy constructor BigObject::Object(const BigObject&) and assignment operator BigObject::operator=(const BigObject&) are defined, but doesn't do what you would suppose at the first glance. They don't create any new objects nor change their values. Instead, they are working with smart references tied to the real perl-side objects (and therefore are very cheap). As usual in perl, the smart references are counted, so that the real object is destroyed only when the last reference goes out of scope. Be sure to use the copy method if you really need an new independent object.

File operations

A “big” object can be loaded from a data file and saved in a new data file:

BigObject p=BigObject::load("filename");
BigObject q("type");
q.save("filename");

These operations are primarily used in standalone programs linked against the callable library, because the C++ client functions called from the rulefiles and scripts usually get their objects as arguments. Please note that save should only be used for objects constructed from scratch, or when you want to store a copy of an object, because any object loaded from a data file is automatically saved during its destruction.

Name and description

These two attributes are mutable, they can be changed at any time.

std::string n=p.name();
std::string txt=p.description();
p.set_name("name");
p.set_description(txt);
p.set_description() << txt << ... ;
p.append_description() << txt << ... ;

Output operators consuming the description can digest everything printable or convertible to text. Strings containing non-ASCII characters must be encoded in UTF-8.

When creating an empty object, the name can be specified as an optional trailing argument of any constructor:

BigObject p("Polytope<Rational>, "name");

Type

BigObjectType t=p.type();

retrieve the current type of the Object. The class ObjectType is described further

p.isa("TightSpan")
p.isa(t)

returns true if the Object kept in p has the type equal to or derived from the one specified literally or by an ObjectType instance

p.cast("TightSpan");
p.cast(t);

Change the type of the Object, returns *this.

The allowance for a type cast is more restricted than that for a copy constructor: only moving along the inheritance line is allowed. Casting to a basis type leads to the lost of properties defined for the original (derived) type only. A subobject can't be cast beyond the type declared for its property in the parent object.

Properties

p.exists("VERTEX_LABELS")

returns true if the property exists; may apply shortcut rules

if (p.lookup("VERTEX_LABELS") >> labels) ...

assigns the property value to a variable and returns true if the property exists

std::string n;
p.lookup_with_property_name("VERTICES | POINTS", n) >> V

as a side effect, the name of the property actually retrieved is stored in a string object

Matrix<Rational> V = p.give("VERTICES");
p.give("VERTICES") >> V;

retrieve the property value. If it does not exist yet, it will be created using production rules; if this is impossible, an exception undefined will be raised.

p.give("VERTICES | POINTS") >> V;

retrieve the value of one of the alternatives, whatever exists or is cheaper to compute

p.give_with_property_name("VERTICES | POINTS", n) >> V;

as a side effect, the name of the property actually retrieved is stored in a string object

p.take("FACETS") << F;

set the property value. Unless the property is declared as mutable, this operation is only allowed during the initialization phase and in production rules having this property among their declared targets.

p.take("FACETS_THRU_VERTICES", temporary) << F;

create a temporary property. It will survive until the end of the execution cycle. In the interactive mode, the cycle ends after the complete evaluation of the last input line; in scripting mode it is the termination of the script.

p.remove("POINTS_IN_FACETS");

remove a property. Unless the property is declared as mutable, it may be removed only if the Object contains enough other data to reconstruct it on later demand. A production rule may remove any property declared as its source.

Subobjects

A subobject is an Object attached to another (parent) Object as a property. The class BigObject is used for accessing subobjects as well as top-level Objects.

BigObject g=p.give("GRAPH");

retrieve a subobject by property name

p.give("GRAPH.ADJACENCY") >> G;

retrieve an atomic property through several layers of hierarchy. Intermediate subobjects (here: GRAPH) do not need to exist up front, they may be created by production rules.

BigObject p=g.parent();

navigate to the parent object

if (p.valid()) ...

for a top-level Object, parent() returns an object in an invalid state

p.take("GRAPH") << g;

attach a complete subobject at once. The passed subobject (here: g) will be copied if one of the following conditions is met:

  • it already belongs to another top-level object
  • it is backed by an XML file
  • copy is requested explicitly:
    p.take("GRAPH") << g.copy();

Otherwise g will be hooked into the parent object as is, loosing its autonomous status.

p.take("GRAPH.ADJACENCY") << G;

set a property in a subobject

p.remove("GRAPH.NODE_DEGREES");

remove a property in a subobject

Multiple subobjects

Multiple subobjects require special methods capable of choosing the desired instance.

  1. adding and removing

    BigObject lp=p.add("LP");

    add a new, empty subobject, to be filled later by calling lp.take(...)

    BigObject lp=p.add("LP", temporary);

    add an empty subobject temporarily

    p.add("LP", lp);

    add a fully initialized subobject

    p.add("LP", lp, temporary);

    add it temporarily

    p.remove(lp);

    remove the specified instance of a subobject; please note the difference to remove("LP") which would remove all instances attached under the given property.

  2. retrieving

    polymake::Array<BigObject> lps = p.give_all("LP");

    all instances at once. The result will be empty if the requested property does not exist in the given object. Note that manipulating the array, for example, deleting some of its elements, would not affect the original objects. Thanks to smart pointers behind the scene they all will survive. But you should in general handle such an Array as const, just in order to avoid confusions and surprises about “unexpected” behavior.

    BigObject lp=p.lookup("LP", "name");

    retrieve an instance by its name

    BigObject sd=p.lookup("SCHLEGEL_DIAGRAM", PolymakeOptions("FACET", 1));

    retrieve an instance with matching values of one or more properties; if nothing matching is found, returns an Object in invalid state.

    sd=p.give("SCHLEGEL_DIAGRAM", PolymakeOptions("FACET", 1));

    retrieve an existing instance or create a new one with the given property values if no matching instance exists yet

    sd=p.give("SCHLEGEL_DIAGRAM", PolymakeOptions("FACET", 1), temporary);

    … a new instance is created temporarily

    sd=p.give("SCHLEGEL_DIAGRAM");

    retrieve an arbitrary instance or try to create one by applying production rules

    The in-line option list may be replaced by a perl::Hash created in advance:
    perl::Hash props;
    props["FACET"] << 1;
    sd=p.give("SCHLEGEL_DIAGRAM", props);

    This form is useful if the number or combination of options may vary at the runtime.

Rule Schedules

BigObject::Schedule s=p.CallPolymakeMethod("get_schedule", "PROPERTY", ...);

Determine the optimal sequence of production rules providing the given properties. Properties of subobjects are specified in the same dotted notations as in the give() call. The object created by this method can be used in the following operations:

s.valid()

Returns true if the rule sequence has been found.

s.apply(q)

Executes the production rules on the given Object. q may be the same as p, that is, the Object used to determine the sequence, or any other Object of the same type and with the same set of properties as p had before the call to get_schedule.

ListResult props=s.list_new_properties();

Returns a list of names of all properties that would be created in the course of executing the rule sequence. Properties in subobjects are encoded in dotted notation. The list will be free of duplicates even if some production rules have common target properties. The order of names in the list is, however, absolutely random.

Sometimes, when get_schedule is called on a subobject, the list might contain rules applicable to the parent object and further ancestors. In this case, the names of properties created there will be reported with a prefix parent. repeated as many times as many levels in the hierarchy they are sitting above the given subobject.

Attachments

Attachments are arbitrary data pieces stored together with the object. They are treated much like mutable properties, that is, they can be added, changed, and removed at any time. Each attachment is identified by its name, which is supposed to be a short string.

p.attach("NAME") << data;
p.take("NAME", attachment) << data;

add or replace the named attachment

if (p.get_attachment("NAME") >> data)

retrieve the named attachment and assigns the value to a variable; returns FALSE if it does not exist

p.remove_attachment("NAME");

remove the named attachment

You can store in attachments data items of primitive types like int or std::string as well as C++ classes declared on the perl side as admissible property types, like Matrix, Set, or std::list. Instances of BigObject are not allowed as attachments.

Clients called within production rules may not access any attachments. Allowing to do so would imply the non-deterministic behavior of rules, because attachments do not contribute to the Object's intrinsic state and can be changed by user.

ObjectType

The class BigObjectType is primarily used as a helper to construct BigObject instances, as shown in examples above. There is not much what could else be done with this class.

BigObjectType t("Polytope<Rational>");

construct a type specified literally

BigObjectType t("Polytope", mlist<CoordType>());

construct a type depending on template parameters of the enclosing function or class

BigObjectType t = p.type();

retrieve the current type of a BigObject

t.name()

get the type name as a std::string, e.g. "Polytope<Rational>"

t.isa("Polytope")

returns true if the type t is equal to the given type, belongs to the given parametrized family, or is derived thereof

t.isa(t2)

checks the derivation relation between two types

Calling Arbitrary Functions and Methods

The following constructions allow to call any function or `big' object method having a declared perl-side interface, that is, declared in some rule file as function, method, user_function, or user_method, regardless whether it's implemented in perl or C++.

call_function("name", arguments...);

call an application-scope function.

Function name may be prefixed with application_name:: if it belongs to another application not imported by the current one.

call_function("name", mlist<Params>(), arguments...);

call a function with explicit type parameters

obj.call_method("name", arguments...);

call a `big' object method

Variable argument lists

Optional (keyword) arguments, ubiquitous in VISUAL functions, can be either passed in-line:

obj.call_method("VISUAL", OptionSet("FacetColor", "blue", "FacetTransparency", 0.5));

or prepared up front:

BigOptionSet opts;
opts["FacetColor"] << "blue";
opts["FacetTransparency"] << 0.5;
opts["VertexLabels"] << Array<std::string>(...);
obj.call_method("VISUAL", opts);

Any container object can be passed elementwise to a function using unroll wrapper:

Array<BigObject> objects;
call_function("name", ..., unroll(objects), ...);

Finally, the argument list can be composed dynamically, providing arguments one by one:

auto prep = prepare_call_function("name");
// or: obj.prepare_call_method("name");
prep << arg1;
prep << arg2;
...
prep();

Consuming results

If the return value of call_function or call_method is not used, like in examples above, the function or method is executed in void context.

If the return value is assigned to a single variable (or, more generally, an lvalue), the function is executed in scalar context:

x = call_function(...);

If the return value is assigned to more than one variable or to an unrolled container, the function is executed in list context:

call_function(...) >> x >> y >> z;
std::vector<std::string> labels;
call_function(...) >> unroll(labels);

The number of values returned by the function does not need to exactly match the number of target variables; excess values are silently dropped, while excess variables stay unchanged. An unrolled container swallows all return values not consumed so far.

Usually a function or method is designed to be called in one specific context. There are, however, functions whose behavior may change depending on the context. For example, all VISUAL methods called in void context launch a visualization tool and show the picture, but in scalar or list contexts they return some funny object which can be passed to other visualization functions.

Calling a function in a “wrong” context does not always has dramatic consequences. The following table lists all possible cases of mismatch:

Executing polymake scripts

The mechanism described above can be used to execute polymake scripts as well. Just call the "script" function, passing to it the script file name and optional arguments:

call_function("script", "my_script_file", x, y, z);

Calling a C++ function over a cached pointer

The power and flexibility of call_function mechanism comes at price of run-time overhead. The arguments must be wrapped into perl objects, then unwrapped on the receiving side, the same for result value. The overload resolution algorithm including type deduction is run anew for every call.

In many cases this overhead can be avoided by using a cached function pointer. The overload resolution and argument wrapping happen only once, at the first use, the cost of subsequent calls is exactly the same as calling a function via a pointer. The cached pointer definition must use the full C++ function signature, including exact const and reference specifications of all arguments and the return value. Usually such a pointer is defined as a local static variable of an inline function wrapping it, which assures the uniqueness and reusability across all clients:

Result call_name(const Arg1& arg1, Arg2 arg2, ...)
{
  static CachedFunctionPointer<Result(const Arg1&, Arg2, ...)> func_ptr("name");
  return func_ptr(arg1, arg2, ...);
}

Using a cached pointer allows to call function templates without injecting their definition body in the caller source code, so that they can be defined in a different application or bundled extension. The function may also have several labeled alternative implementations selectable by preference lists. The cached pointer is automatically following the changes introduced by user commands prefer, prefer_now, and reset_preference.

There is, however, a restriction posed on the eligible functions: they must not have keyword arguments (OptionSet), arguments with default values or explicit type parameters. The reason for this restriction is that all this is processed in the perl layer which is bypassed when calling over a pointer.

An example of use of a cached function pointer for an overloaded function is solve_LP in application polytope.

Miscellaneous functions

var = get_custom("name")

Retrieve the value of a custom variable. The name must be fully qualified with the package name unless it's defined in the package of the same application as this code belongs to. If the custom variable is of an array or hash map type, the C++ local variable being assigned to must be of type ListResult resp. OptionSet.

var = get_custom("name", "key")

Retrieve the value of an element of a custom hash map variable.

save_data("filename", var, "description");

Store an item of a data type known as a property type (not a “big” object!) in an XML file. The description string is optional.

var = load_data("filename");

Retrieve the data stored in an XML file created with save_data.

All retrieval functions, similar to Object::give(), can also be used with an “input” operator >> :
get_custom("name") >> var;

int d = get_debug_level();

Get the current value of the global variable $DebugLevel, which is initially set according to the occurrence of -d command-line options of polymake main script. A C++ client may decide to produce additional debugging output depending on this value.

Connection to perl

There are several macros helping to form the glue between C++ and perl worlds. The most important of them are four macros for functions: Function4perl, FunctionTemplate4perl, UserFunction4perl, and UserFunctionTemplate4perl.

UserFunction... macros differ from the corresponding Function... macros by one additional leading argument, which must be a (very long) string literal with help text, looking much like the comments in the rule files. The following example is the abridged help text from the client polytope::cube :

"# @category Producing from scratch"
"# Produce a //d//-dimensional cube."
"# @param Int d the dimension"
"# @return Polytope",

Please note the comma after the very end of the string: it separates the help text from the following macro argument, which might also be a string literal in some cases.

The behavior of functions declared with or without User prefix is exactly the same; the only difference is that user functions appear in the TAB completion lists, as well as in the interactive help and on the reference documentation pages, while the “bare” functions stay invisible to the user (but she can still call the latter from the interactive shell or from scripts, if she knows about them). All examples and explanations following below are equally applicable to both pairs of macros.

Function4perl is used to bind regular C++ functions. It has two arguments: the first one is the address of the C++ function (like &cube), the second one is a string defining the perl-side signature much like it is done in the rulefiles.

FunctionTemplate4perl is used to bind function templates. It has only one argument, namely the string containing the signature and optional attributes describing the return value. The perl-side name specified in the signature must be identical to the C++ name.

The perl-side arguments defined in the signature must match the parameters of the C++ function. The matching rules are, however, flexible and allow for quite elaborate mapping between the perl and C++ worlds. You are even allowed to map several perl signature to the same C++ function (by writing several Function macros with different signatures) or map one single perl signature to several C++ functions (by providing overloaded instances of the latter in your source code). Below you can find the matching rules for all possible elements of a signature:

$

The simple scalar designator swallows everything on the perl side. The corresponding parameter of the C++ function must be one of the following:

  • Elementary types bool, int, long, double, char, and std::string
  • Container type like Set, Vector, or Array, if you expect the caller to provide typeless anonymous arrays like [1,2,…] or string literals with suitable formatting like "{1 2}" for a Set.
  • Multi-dimensional containers like Matrix under similar circumstances, i.e. when you expect arguments like nested anonymous arrays [[1,2],[3,4]] or multi-line strings.

If the actual value passed from the perl side does not fit the C++ parameter type, an exception is raised.

*

The type wildcard swallows everything as well. It is only allowed in FunctionTemplate4perl. The actual value passed from the perl side is analyzed during the call dispatch, where the appropriate C++ type declaration is constructed and used to generate the suitable wrapper source code. The corresponding parameter of the C++ function must be a free template type parameter. Alternatively, you may provide several specializations of the C++ function handling different cases.

If the actual argument passed from the perl side does not have a corresponding C++ type or would lead to an ill-formed function template instantiation (e.g. because no matching C++ specialization exists), so that the compilation of the generated wrapper code fails, an exception is raised.

PropertyType

A regular property type declared in the rulefiles with the c++ attribute (or a concrete instance of a parametrized type). This element swallows any object of that type or derived thereof. The corresponding parameter of the C++ function must be of the type declared in the c++ attribute, possibly passed by a const reference.

ParametrizedPropertyType

A parametrized property type declared in the rulefiles with the c++ attribute. This element swallows any object of any type instantiated from this parametrized type or derived thereof. It is only allowed in FunctionTemplate4perl. The actual value passed from the perl side is analyzed like in the wildcard case, that is, for each new concrete type instance a new wrapper will be generated. The corresponding parameter of the C++ function must be one of the following:

  • a free template type parameter
  • the C++ class template as declared in the rulefiles, parametrized with free template type parameters
  • the class template of a generic family embracing the class declared in the rulefiles

in all cases, possibly passed by a const reference.

For example, the signature func(Matrix) matches any of the following C++ functions (return types do not matter here):
template <typename X> int func(X);
template <typename X> int func(const Matrix<X>&);
template <typename M, typename X> int func(const GenericMatrix<M,X>&);

ObjectType

A `big' polymake object is always kept in a C++ object of class BigObject. Both concrete types and parametrized types can be used:

  • int func(BigObject o);
    Function4perl(func, "func(Polytope)");
    any Polytope would match
  • int func(BigObject o);
    Function4perl(func, "func(Polytope<Rational>)");
    only Polytope with Rational coordinates would match
  • template <typename Coord> int func(BigObject o);
    FunctionTemplate4perl("func<Coord>(Polytope<Coord>)");
    any Polytope would match; the coordinate type will be included into the generated wrapper code as an explicit type parameter.
&

A signature element with this modifier must correspond to a parameter of the C++ function passed by a non-const reference. Trying to pass a write-protected object property to such a function will cause an exception.

&&

A signature element with this modifier must correspond to a “universal reference” parameter of the C++ function. The function may be instantiated with different reference types (const reference, non-const refernce, rvalue), depending on the data passed as an argument (write-protected object property or mutable value, value stored in a variable or a temporary value).

Alternatively, there can be two distinct C++ functions, one with a const reference parameter, another one with a non-const reference parameter.

&& const

Like above, but mapping non-const non-temporary values to const references.

;

Optional arguments must have default values, otherwise an exception will be raised by an attempt to initialize the C++ parameter with an undef value. The corresponding parameters of the C++ function themselves do not need to be declared with default values, because the C++ function will always be called with a full list of arguments.

+

An element with this modifier swallows a list of arbitrarily many objects of matching type. The corresponding argument of the C++ function must be an appropriate container like Set, Array, or std::vector.
For “big” objects like Polytope the container type must be Array<BigObject>. An attempt to use any other container will fail miserably.

@

This element swallows all trailing arguments of any elementary types, even of different ones. The corresponding argument of the C++ function must be a container. If the type of any of the trailing argument does not match the element type of this container (and can't be converted to it), an exception is raised. Don't define the C++ function with ellipsis parameter ... !

{ option_key => default_value, ... }

The keyword-value pairs are gathered in a hash map before the C++ function is called, the corresponding parameter must be of type BigOptionSet. Options with undef default values are not stored in the OptionSet unless explicitly specified by the caller. Options with other default values are always be passed to the C++ function.

Injecting Rules into Clients

When an application is loaded, every client is processed like a mini-rulefile, after all regular rulefiles. You can embed any perl code or special rules recognized by polymake directly into a client, using a macro

InsertEmbeddedRule("rule text\n"
                   "extending over several lines\n"
                   "# including some comments\n");

In practice, only few use cases justify embedding into clients, because such rules are not easy to spot and every change would require a recompilation of the client library:

Note that CREDIT and REQUIRE rules must precede the Function macros in the client source code in order to take effect on them. Please also keep in mind that the clients in an application are processed in an arbitrary order, thus no client should refer in its embedded rules on anything introduced in another client of the same application and extension.

Building and Debugging

As already mentioned at the very beginning, the client source code always resides in the application src directory. It can only be built together with other clients of its application or extension. Just run ninja -C build/Opt at the top directory of the polymake source tree resp. of the extension. Run ninja -C build/Debug if you are going to run it under the debugger.

When your client is called for the first time, it might require a new wrapper for transforming the arguments from perl representation to C++ and the results in the opposite direction. This wrapper will be generated automatically, compiled on the fly, and loaded as a separate dynamic module. Unless you set the custom variable $Verbose::cpp to 1 or higher, you won't notice anything but some delay in execution. But, if your client is a function template, it will be really instantiated only now, so that some compiler errors might appear. To fix the errors, you don't need to leave the running polymake session in most cases! Just edit the sources of your client and repeat the call. You will only have to quit the session if you make any changes in the glue declarations like Function4perl.

When you quit the session later, after having fixed all compiler problems, the automatically created wrapper will be written into a separate source file, located in the subdirectory cpperl of the application. Next time polymake is started, it will recompile the client together with the wrapper once again while loading the application.

If your extension is managed with source version control like git, remember to eventually commit all changes in the cpperl subdirectory.

To debug a client, you must start the whole perl interpreter under gdb. Before you do this for the first time, you should store two useful settings in ~/.gdbinit:

set breakpoint pending on
set env POLYMAKE_BUILD_MODE=Debug

The first one allows to set breaking points without having the binary code completely loaded into memory. This is important, because your client is compiled into a dynamic module which will be loaded together with its application. The second one provides loading dynamic modules compiled with debugging options (remember ninja -C build/Debug!)

Now you can start polymake as follows:

gdb -args perl -S polymake

If you are debugging a client compiled in the working copy of the polymake source tree, which is probably not contained in your PATH, then you should omit the option -S and specify the full path to /your/working/copy/perl/polymake instead.

As soon as you see the (gdb) prompt, set a breakpoint in your client's code:

b my_client.cc:123

The debugger will complain about unknown source file, but nevertheless will take note of the breakpoint. Set more breakpoints if needed. You can also set a breakpoint for events (check gdb manual). With the following command a breakpoint is set for the event that an exception is thrown:

b __cxa_throw

Then start the program with the usual run command. When the applications are loaded and the polymake prompt appears, trigger the execution of your client, either by calling it directly (if it is declared as a user function), or by executing a rule it's used in. polymake will stop at one of your breakpoints.

The most popular data structures defined in the template library, like Matrix, Set, Array, or Graph, offer useful dump() methods when compiled with debugging support. They can be called right away from the gdb prompt:

(gdb) call M.dump()

The output goes to stderr, thus be sure not to redirect it (polymake never does it on its own).