Skip to content

Instantly share code, notes, and snippets.

@kalmarek
Created November 16, 2018 16:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kalmarek/c5a38f733903753a72abedf1dcd63ebb to your computer and use it in GitHub Desktop.
Save kalmarek/c5a38f733903753a72abedf1dcd63ebb to your computer and use it in GitHub Desktop.
Wrapping a new type to Polymake.jl

So here You are, our promises led you to a land flowing with julia and Polymake, and yet when You tried our new fancy stuff, instead of getting a new shiny result You slam into a warning, which reads

┌ Warning: The return value contains (pm::SomeType<pm::with::parameters>) which has not been wrapped yet
└ @ Polymake ~/.julia/dev/Polymake/src/functions.jl:75
(insert some string representation here)

In this document we will guide You what does this warning mean, how to make use of the retured object and how to become a Polymake.jl contributor who wrapped yet another small object from the polymake small objects zoo.

Do I really need to wrap the the thing?

It very much depends what You intend to do with the returned PropertyValue. If You just need to pass it into another polymake function, You don't have to do anything!. Consider:

julia> polytope.isomorphic(cube(3).GRAPH.ADJACENCY, cube(2).GRAPH.ADJACENCY)
0

This will work even if pm::graph::Graph<pm::graph::Undirected> (which is the C++ type of ADJACENCY) is not wrapped in Polymake.jl. The important thing is that the returned type (int in this case) is wrapped.

However, if You need to interact directly with the small type stored inside, there is nothing You can do about it. If You look closely at the returned object You soon encounter that the returned object is of type Polymake.pm_perl_PropertyValueAllocated.

What is this pm_perl_PropertyValueAllocated?

It's the julia type which wrapps the pm::perl::PropertyValue object on the polymake side.

It's an opaque container. It can hold anything. But since You are here it most probably contains a pointer to a concrete instance of a native C++ class/struct. We know what it is, since pm::perl::PropertyValue contains also a string description, which You can access by calling Polymake.typeinfo_string() on it (this is what is done in the warning). On the polymake side this description is used to figure out the content, unpack it and e.g. pick a C++ function with the correct signature.

Unfortunately the same mechanics needs to be replicated on the julia side, and You just encountered an instance of pm_perl_PropertyValue which we don't know how to unpack yet. Are You thrilled to teach Polymake.jl a new trick?

Setting on the journey

First You need to determine if all types which parametrize pm::SomeType<pm::with::parameters> has already been wrapped (i.e. are available natively in Polymake.jl). The growing list of wrapped types is located here, but most of the Basic Types of polymake has already been wrapped. If it happens so, please continue. If not, You need to recurse on the parameter type...

Brief overview

Let us assume that we work with type pm::SomeType<S,T> with parameters S and T. The first brief overview of what needs to be done is as follows:

  1. ] dev https://github.com/oscar-system/Polymake.jl
  2. Write C++ code exposing pm::SomeType to julia (through libcxxwrap-julia)
  3. Register the concrete wrapped C++ types in C++ and in julia
  4. Write the julia code which interfaces the C++ code.

Out of those the hardest point is (of course) writing the C++ code. The type registration may seem to be complicated, but it's just a bit technical (and there are four places for the various incarnations of the type to be inserted). Finally writing the julia code is more of a user-experience job and shouldn't pose any serous trouble (unless You really care about Your users. As You can see, we don't ;)

Not so Brief overview

C++ code wrapping pm::SomeType

The C++ code wrapping pm::SomeType is best to be placed in deps/src/polymake_sometypes.cpp. You need to choose a julia name for the exposed type here, and we'd chosen "pm_SomeType" convention. Your type will appear in julia as pm_SomeType{S, T} then. Unfortunately we generate static code and static list of types, which means that the exact values of available Ses and Ts need to be determined at compile time (i.e. hardcoded in deps/src/polymake_sometypes.cpp).

These are the basic steps:

  • add deps/src/polymake_sometypes.h with

    void polymake_module_add_sometype(jlcxx::Module& polymake);

    (and possibly other standalone functions related to SomeType)

  • add calls

    polymake_module_add_sometype(polymake);
    POLYMAKE_INSERT_TYPE_IN_MAP(pm_SomeType_instanceofS_instanceofT)

    in define_module_polymake function in deps/src/polymake.cpp

  • wrap the type in deps/src/polymake_sometypes.cpp, i.e. write polymake_module_add_sometype(polymake) function which add_types to the module polymake. More details on this below.

Registering the pm_SomeType type

Registering the type involves inserting it into four maps/lists in various places:

  • in src/Polymake.jl add the concrete instances of wrapped pm_SomeType to C_TYPES, e.g. add

    ("pm_SomeType_instanceofS_instanceofT", pm_SomeType{instanceofS, instanceofT}).

    This pm_SomeType_instanceofS_instanceofT will serve as the identifief or the concrete instantiation of pm::SomeType{S,T} on the C++ side

  • in deps/src/polymake_caller.h You need to create jl_value_t pointing to the pm_SomeType, ie. add

    CreatePolymakeTypeVar(pm_SomeType_instanceofS_instanceofT)

    just below all the other known types

  • in deps/src/polymake_caller.cpp add

    `TO_POLYMAKE_FUNCTION(pm_SomeType_instanceofS_instanceofT, pm::SomeType<S,T>)``

  • in src/functions.jl insert

    (Symbol("pm::SomeType<pm::with::parameters>"), pm_SomeType{S,T})

    into WrappedTypes dictionary. Note that the key needs to be exactly as appearing in the warning (we will be comparing strings here), with all spaces removed.

What to put in polymake_module_add_sometype

The first thing You should add is

polymake.add_type<jlcxx::Parametric<jlcxx::TypeVar<1>>, jlcxx::Parametric<jlcxx::TypeVar<2>>>("pm_SomeType")
  .apply<
    pm::SomeType<instanceofS, instanceofT>,
    pm::SomeType<instance2ofS, instance2ofT>
  >([](auto wrappedType){
    ...
  });

This produces a lambda (anonymous function) which wraps two very concrete realisations of pm::SomeType<S,T>. Inside the {...} You may access the passed type as decltype(wrappedType)::type and it is advised (for readibility) to define

typedef typename decltype(wrapped)::type pm_SomeType;

You can then use pm_SomeType as type in the signature of functions You are about to define. Standard constructors are defined automatically, and we refer to CxxWrap documentation for more information on how to alter/change this behaviour.

If wrappedType has already ::size method You may point a julia function directly to it, i.e.

wrappedType.method("length", &pm_SomeType::size);

will register function length (ready to be called from julia!) which will take x of type pm_SomeType as the first argument and return the result of a call to .size() method.

Standalone functions/operators can be defined similarly, e.g.

wrappedType.method("==", [](const pm_SomeType& S, const pm_SomeType& T){return S == T;});

produces a native julian method of == that can be used to compare instances of pm_SomeTypes. Note: this defines a method only for comparing x::pm_SomeType{S,T} and y::pm_SomeType{S,T}, parametrized by exaclty the same types! To handle more we advise writing promotion rules and relying on julias == automatic promotion. Or if performance is important writing a specialized C++ lambda for the appropriate types.

Minimal content of add_type(...)

  • to get automatic printing provide to_small_obj function on the julia side. Just copy the folowing snippet:
wrapped.method("show_small_obj", [](const WrappedT& A){
    return show_small_object<WrappedT>(A);
});
  • to allow unpacking pm::perl::PropertyValue which contains an pm_SomeType provide
polymake.method("to_SomeType_instanceofS_instanceofT",
    [](pm::perl::PropertyValue v){
    return to_SmallObject<pm::SomeType<instanceofS, instanceofT>>(v);
});

*for all concrete instantiations of pm_SomeType. You also need to add a mapping from Symbol to to_SomeType_instanceofS_instanceofT in WrappedTypes in src/functions.jl.

Advise on writing julia code wrapping types

  • You can make "pm_SomeType" a subtype of externally defined abstract type by providing a second argument to add_type call, e.g. to make pm_SomeType "array-like" modify the call above to
polymake.add_type<jlcxx::Parametric<jlcxx::TypeVar<1>>, jlcxx::Parametric<jlcxx::TypeVar<2>>>("pm_SomeType"), jlcxx::julia_type("AbstractSet", "Base"))
  • the C++ side of the wrapped type is rather rigid, so it's best to write a small set of low-level functions in C++ and add all the user convenience on the julia side. As an example consider function setindex!(x::pm_SomeType, val, idx). A user would expect the val to be automatically converted to eltype(x) and the indexing be independent of the integer type of idx. Thus it is best to add a single method
typedef typename decltype(wrapped)::type::value_type elemType;

wrapped.method("_setindex!",[](pm_SomeType& A, const elemType& val, int64_t n){
    A[static_cast<long>(n) - 1] = val;
});

(note the index shift!) and wrap it on the julia side as

function Base.setindex!(A::pm_SomeType{T, S}, val, n::Integer) where {T,S}
    1 <= n <= length(A) || throw(BoundsError(A, n))
    _setindex!(A, eltype(A)(val), Int(n))
    return A
end

which covers (with slight overhead) all possible uses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment