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
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.
-
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
.
-
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 aPolytope<Rational>
:BigObject q("Polytope<Float>", p)
will convertVERTICES
,FACETS
, and all other coordinate-related properties fromMatrix<Rational>
intoMatrix<Float>
,Vector<Rational>
intoVector<Float>
, etc.BigObject g=p.give("GRAPH");
BigObject q=g.copy();
won't copyEDGE_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 inp
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.
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.
-
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 prefixparent.
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 typet
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:
- void function → scalar context: no assignment is made to the target variable
- void function → list context: receives an empty list; when used in a chained
>>
operator, no target variable is changed - scalar function → list context: receives a list with a single element
- list function → scalar context: the last element of the list is assigned to the target variable, the rest is discarded
- scalar or list function → void context: the results are discarded
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 ofpolymake
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
, andstd::string
- Container type like
Set
,Vector
, orArray
, if you expect the caller to provide typeless anonymous arrays like[1,2,…]
or string literals with suitable formatting like"{1 2}"
for aSet
. - 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 thec++
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 inFunctionTemplate4perl
. 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 matchint func(BigObject o);
Function4perl(func, "func(Polytope<Rational>)");
only Polytope with Rational coordinates would matchtemplate <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
, orstd::vector
.
For “big” objects likePolytope
the container type must beArray<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 withundef
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:
- providing short pure perl overloaded function instances complementing the client functions defined in this client, e.g. convenience wrappers performing input and/or result transformations.
- introducing credit notes specific for this client
- imposing restrictions on the use of this client like REQUIRE_EXTENSION or REQUIRE_APPLICATION.
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).