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.
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
.
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?
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...
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:
] dev https://github.com/oscar-system/Polymake.jl
- Write C++ code exposing
pm::SomeType
to julia (throughlibcxxwrap-julia
) - Register the concrete wrapped C++ types in C++ and in julia
- 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 ;)
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 S
es and T
s 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
withvoid 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 indeps/src/polymake.cpp
-
wrap the type in
deps/src/polymake_sometypes.cpp
, i.e. writepolymake_module_add_sometype(polymake)
function whichadd_type
s to the modulepolymake
. More details on this below.
Registering the type involves inserting it into four maps/lists in various places:
-
in
src/Polymake.jl
add the concrete instances of wrappedpm_SomeType
toC_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 ofpm::SomeType{S,T}
on the C++ side -
in
deps/src/polymake_caller.h
You need to createjl_value_t
pointing to thepm_SomeType
, ie. addCreatePolymakeTypeVar(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 comparingstrings
here), with all spaces removed.
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_SomeType
s. 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.
- 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 anpm_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
.
- You can make "pm_SomeType" a subtype of externally defined abstract type by providing a second argument to
add_type
call, e.g. to makepm_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 theval
to be automatically converted toeltype(x)
and the indexing be independent of the integer type ofidx
. 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.