Polymake.jl
is a Julia package for using polymake
, a software for research in polyhedral geometry from Julia.
This package is developed as part of the OSCAR project.
The current version of Polymake.jl
relies on polymake
version 3.3
or later.
Index:
The installation can be done in the Julia's REPL by executing
julia> using Pkg; Pkg.add("Polymake")
This will fetch a pre-built binary of polymake
. You are ready to start using Polymake
.
Note: Pre-built binaries are available for the Linux
and macOS
platform, but the macOS
binaries are considered experimental. Windows users are encouraged to try running Julia inside Window Subsystem for Linux and reporting back ;)
To skip the test for polymake-config
in the PATH
and directly use the pre-built binaries you need to set POLYMAKE_CONFIG=no
in your environment.
Note: Pre-built polymake will use a separate .polymake
config directory (usually joinpath(homedir(), ".julia", "polymake_user")
).
If you already have a recent enough version of polymake
(i.e. >=3.3
) on your system,
you either need to make polymake-config
available in your PATH
or
the environment variable POLYMAKE_CONFIG
needs to point to the correct
polymake-config
file.
After this just add
the package as above (or build
if the package has been already added), the build script will use your polymake
installation.
A compatible version is available at polymake/polymake.
It can be compiled as follows where GIT_FOLDER
and INSTALL_FOLDER
have to be substituted with your favorite folders. Please note that these need to be absolute paths.
Also make sure to check the necessary dependencies as well as the additional instructions for Macs.
export POLYMAKE_GIT=GIT_FOLDER
export POLYMAKE_INSTALL=INSTALL_FOLDER
git clone [email protected]:polymake/polymake $POLYMAKE_GIT
cd $POLYMAKE_GIT
./configure --prefix=$POLYMAKE_INSTALL
ninja -C build/Opt
ninja -C build/Opt install
export POLYMAKE_CONFIG=$POLYMAKE_INSTALL/bin/polymake-config
Note that polymake
might take some time to compile.
After this start Julia and follow the instructions above.
Note: Self-built polymake will use the standard .polymake
config directory (usually $HOME/.polymake
).
First clone Polymake.jl
:
git clone https://github.com/oscar-system/Polymake.jl.git
In the same directory start Julia and press ]
for pkg
mode. Then run
(v1.0) pkg> activate Polymake.jl
(Polymake) pkg> instantiate
(Polymake) pkg> build Polymake # fetches the prebuild polymake binaries
(Polymake) pkg> test Polymake # and You are good to go!
If polymake-config
is in your PATH
, or POLYMAKE_CONFIG
environment variable is set the build
phase will try to use it.
Just remember that You need to activate Polymake.jl
to use Polymake
.
In this section we just highlight various possible uses of Polymake.jl
. Please refer to Polymake syntax translation for more thorough treatment.
polymake
big objects (like Polytope
, Cone
, etc) should be created with the help of @pm
macro:
# Call the Polytope constructor
julia> p = @pm Polytope.Polytope(POINTS=[1 -1 -1; 1 1 -1; 1 -1 1; 1 1 1; 1 0 0])
type: Polytope<Rational>
POINTS
1 -1 -1
1 1 -1
1 -1 1
1 1 1
1 0 0
Parameters could be passed as
- keyword arguments (as above),
Pair{Symbol, ...}
s e.g.Polytope.Polytope(:POINTS=>[ ... ])
- dictionaries e.g.
Polytope.Polytope(Dict( "POINTS" => [ ... ] )
)
The dictionary may hold many different attributes. All the names must be compatible with polymake
.
Properties of such objects can be accessed by the .
syntax:
julia> p.INTERIOR_LATTICE_POINTS
pm::Matrix<pm::Integer>
1 0 0
The following script is modelled on the one from the Using Perl within polymake tutorial:
using Polymake
str = read("points.demo", String)
# eval/parse is a hack for Rational input, don't do this at home!
matrix_str = "["*replace(str, "/"=>"//")*"]"
matrix = eval(Meta.parse(matrix_str))
@show matrix
p = @pm Polytope.Polytope(POINTS=matrix)
@show p.FACETS # polymake matrix of polymake rationals
@show Polytope.dim(p) # Julia Int64
# note that even in Polymake property DIM is "fake" -- it's actually a function
@show p.VERTEX_SIZES # polymake array of ints
@show p.VERTICES
for (i, vsize) in enumerate(p.VERTEX_SIZES)
if vsize == Polytope.dim(p)
println("$i : $(p.VERTICES[i,:])")
# $i will be shifted by one from the polymake version
end
end
simple_verts = [i for (i, vsize) in enumerate(p.VERTEX_SIZES) if vsize == Polytope.dim(p)] # Julia vector of Int64s
special_points = p.VERTICES[simple_verts, :] # polymake Matrix of rationals
@show special_points;
The script included (i.e. in running REPL execute include("example_script.jl");
) produces the following output:
matrix = Rational{Int64}[1//1 0//1 0//1 0//1; 1//1 1//16 1//4 1//16; 1//1 3//8 1//4 1//32; 1//1 1//4 3//8 1//32; 1//1 1//16 1//16 1//4; 1//1 1//32 3//8 1//4; 1//1 1//4 1//16 1//16; 1//1 1//32 1//4 3//8; 1//1 3//8 1//32 1//4; 1//1 1//4 1//32 3//8]
p.FACETS = pm::Matrix<pm::Rational>
0 -1 20/7 8/7
0 -1 20 -1
0 20/7 -1 8/7
0 20/7 8/7 -1
0 20 -1 -1
1 16/3 16/3 -20/3
0 8/7 20/7 -1
0 8/7 -1 20/7
1 16/3 -20/3 16/3
0 -1 -1 20
0 -1 8/7 20/7
1 -20/3 16/3 16/3
1 -32/21 -32/21 -32/21
(Polymake.Polytope).dim(p) = 3
p.VERTEX_SIZES = pm::Array<int>
9 3 4 4 3 4 3 4 4 4
p.VERTICES = pm::Matrix<pm::Rational>
1 0 0 0
1 1/16 1/4 1/16
1 3/8 1/4 1/32
1 1/4 3/8 1/32
1 1/16 1/16 1/4
1 1/32 3/8 1/4
1 1/4 1/16 1/16
1 1/32 1/4 3/8
1 3/8 1/32 1/4
1 1/4 1/32 3/8
2 : pm::Vector<pm::Rational>
1 1/16 1/4 1/16
5 : pm::Vector<pm::Rational>
1 1/16 1/16 1/4
7 : pm::Vector<pm::Rational>
1 1/4 1/16 1/16
special_points = pm::Matrix<pm::Rational>
1 1/16 1/4 1/16
1 1/16 1/16 1/4
1 1/4 1/16 1/16
As can be seen we show consecutive steps of computations: the input matrix
, FACETS
, then we ask for VERTEX_SIZES
, which triggers the convex hull computation. Then we show vertices and print those corresponding to simple vertices. Finally we collect them in special_points
.
Note that a polymake
matrix tries to mimic the behaviour of Julia arrays: p.VERTICES[2,:]
returns a 1
-dimensional slice (i.e. pm_Vector
), while passing a set of indices (p.VERTICES[special_points, :]
) returns a 2
-dimensional one.
The same minor (up to permutation of rows) could be obtained by using sets: either Julia or polymake sets. However since by default one can not index arrays with sets, we need to collect them first:
simple_verts = Set(i for (i, vsize) in enumerate(p.VERTEX_SIZES) if vsize == Polytope.dim(p)) # Julia set of Int64s
simple_verts = pm_Set(i for (i, vsize) in enumerate(p.VERTEX_SIZES) if vsize == Polytope.dim(p)) # polymake set of longs
special_points = p.VERTICES[collect(simple_verts), :]
The following tables explain by example how to quickly translate polymake
syntax to Polymake.jl
.
Polymake | Julia |
---|---|
$p (reference to 'scalar' variable) |
p (reference to any variable) |
print $p; |
print(p) or println(p) or @show p , or just p in REPL |
$i=5; $j=6; |
i,j = 5,6 or i=5; j=6 ( ; is needed for separation, can be used to suppress return value in REPL) |
$s = $i + $j; print $s; |
s = i + j |
Polymake | Julia |
---|---|
Linear containers with random access | Linear containers with random access + all the algebra attached |
@A = ("a", "b", "c"); |
A = ["a", "b", "c"] |
$first = $A[0]; ( first is equal to a ) |
first = A[1] (note the 1 -based indexing!) |
@A2 = (3,1,4,2); |
A2 = [3,1,4,2] |
print sort(@A2); (a copy of A2 is sorted) |
println(sort(A2)) (to sort in place use sort!(A2)) |
$arr = new Array<Int>([3,2,5]); (a C++ object) |
arr = [3,2,5] (the Int type is inferred) |
$arr->[0] = 100; (assignment) |
arr[1] = 100 (assignment; returns 100 ) |
Polymake | Julia |
---|---|
%h = (); |
h = Dict() it is MUCH better to provide types e.g. h = Dict{String, Int}() |
$h{"zero"}=0; $h{"four"}=4; |
h["zero"] = 0; h["four"] = 4 (call returns the value) |
print keys %h; |
@show keys(h) (NOTE: order is not specified) |
print join(", ",keys %hash); |
join(keys(h), ", ") (returns String ) |
%hash=("one",1,"two",2); |
Dict([("one",1), ("two",2)]) (will infer types) |
%hash=("one"=>1,"two"=>2); |
Dict("one"=>1,"two"=>2) |
Polymake | Julia |
---|---|
Balanced binary search trees | Hash table with no content |
$set=new Set<Int>(3,2,5,3); |
set = Set{Int}([3,2,5,3]) |
print $set->size; |
length(set) |
@array_from_set=@$set |
collect(set) (NOTE: this creates a Vector , but order is NOT specified) |
Polymake | Julia |
---|---|
new Matrix<T> Container with algebraic operations |
Matrix{T} = Array{T, 2} Linear container with available indexing by 2 -ples; all algebra attached |
$mat=new Matrix<Rational>([[2,1,4,0,0],[3,1,5,2,1],[1,0,4,0,6]]); $row1=new Vector<Rational>([2,1,4,0,0]); $row2=new Vector<Rational>([3,1,5,2,1]); $row3=new Vector<Rational>([1,0,4,0,6]); @matrix_rows=($row1,$row2,$row3); (Perl object)$matrix_from_array=new Matrix<Rational>(\@matrix_rows); (C++ object) |
mat = Rational{Int}[2 1 4 0 0; 3 1 5 2 1; 1 0 4 0 6]; row1 = Rational{Int}[2, 1, 4, 0, 0]; row2 = Rational{Int}[3, 1, 5, 2, 1]; row3 = Rational{Int}[1, 0, 4, 0, 6]; matrix_rows = hcat(row1', row2', row3') (Julia stores matrices in column major format, so ' i.e. transposition is needed) |
$mat->row(1)->[1]=7; $mat->elem(1,2)=8; |
mat[2,2] = 7; mat[2,3] = 8 |
$unit_mat=4*unit_matrix<Rational>(3); |
unit_mat = Diagonal([4//1 for i in 1:3]) or UniformScaling(4//1) depending on application; both require using LinearAlgebra |
$dense=new Matrix<Rational>($unit_mat); $m_rat=new Matrix<Rational>(3/5*unit_matrix<Rational>(5)); $m2=$mat/$m_rat; $m_int=new Matrix<Int>(unit_matrix<Rational>(5)); $m3=$m_rat/$m_int; (results in an error due to incompatible types) |
Array(unit_mat) m_rat = Diagonal([3//5 for i in 1:5]) m2 = mat/m_rat m_int = Diagonal([1 for i in 1:5]) m_rat/m_int (succeeds due to promote happening in / ) |
convert_to<Rational>($m_int) $z_vec=zero_vector<Int>($m_int->rows) $extended_matrix=($z_vec|$m_int); (adds z_vec as the first column, result is dense) |
convert(Diagonal{Rational{Int}}, m_int) z_vec = zeros(Int, size(m_int, 1)) extended_matrix = hcat(z_vec, m_int) (result is sparse) |
$set=new Set<Int>(3,2,5); $template_Ex=new Array<Set<Int>>((new Set<Int>(5,2,6)),$set) |
set = Set([3,2,5]); template_Ex = [Set([5,2,6]), set] |
Polymake | Julia |
---|---|
$p=new Polytope<Rational>(POINTS=>cube(4)->VERTICES); |
p = @pm Polytope.Polytope(POINTS=Polytope.cube(4).VERTICES) |
$lp=new LinearProgram<Rational>(LINEAR_OBJECTIVE=>[0,1,1,1,1]); |
lp = @pm Polytope.LinearProgram(LINEAR_OBJECTIVE=[0,1,1,1,1]) |
$p->LP=$lp; $p->LP->MAXIMAL_VALUE; |
p.LP = lp p.LP.MAXIMAL_VALUE |
$i = ($p->N_FACETS * $p->N_FACETS) * 15; |
i = (p.N_FACETS * p.N_FACETS) * 15 |
$print p->DIM; |
Polytope.dim(p) DIM is actually a faux property, which hides a function beneath |
application "topaz"; $p = new Polytope<Max, QuadraticExtension>(POINTS=>[[1,0,0], [1,1,0], [1,1,1]]); |
p = @pm Tropical.Polytope{Max, QuadraticExtension}(POINTS=[1 0 0; 1 1 0; 1 1 1]) |
- Big objects, e.g., Polytopes, can be handled in Julia.
- Several small objects (data types) from
polymake
are available inPolymake.jl
:- Integers (
pm_Integer <: Integer
) - Rationals (
pm_Rational <: Real
) - Vectors (
pm_Vector <: AbstractVector
) ofpm_Integer
s andpm_Rational
s - Matrices (
pm_Matrix <: AbstractMatrix
) ofFloat64
s,pm_Integer
s andpm_Rational
- Sets (
pm_Set <: AbstractSet
) ofInt32
s andInt64
s - Arrays (
pm_Array <: AbstractVector
, aspm_Arrays
are one-dimensional) ofInt32
s,Int64
s andpm_Integers
- some combinations thereof, e.g.,
pm_Array
s ofpm_Sets
ofInt32
s.
- Integers (
These data types can be converted to appropriate Julia types, but are also subtypes of the corresponding Julia abstract types (as indicated above), and so should be accepted by all methods that apply to the abstract types.
Note: If the returned small object has not been wrapped in Polymake.jl
yet, you will not be able to access its content or in general use it from Julia,
however you can always pass it back as an argument to a polymake
function.
Moreover you may try to convert to Julia understandable type via
@pm Common.convert_to{wrapped{templated, type}}(obj)
.
For example:
julia> c = Polytope.cube(3);
julia> f = c.FACETS;
julia> f[1,1] # f is an opaque pm::perl::PropertyValue to julia
ERROR: MethodError: no method matching getindex(::Polymake.pm_perl_PropertyValueAllocated, ::Int64, ::Int64)
Stacktrace:
[...]
julia> m = @pm Common.convert_to{Matrix{Integer}}(f)
pm::Matrix<pm::Integer>
1 1 0 0
1 -1 0 0
1 0 1 0
1 0 -1 0
1 0 0 1
1 0 0 -1
julia> m[1,1]
1
- All user functions from
polymake
are available in the appropriate modules, e.g.homology
function fromtopaz
can be called asTopaz.homology(...)
in Julia. We pull the docstrings for functions frompolymake
as well, so?Topaz.homology
(in Julia's REPL) returns apolymake
docstring. Note: the syntax presented in the docstring is apolymake
syntax, notPolymake.jl
one. - Most of the user functions from
polymake
are available asAppname.funcname(...)
inPolymake.jl
. Moreover, any function frompolymake
C++
library can be called via@pm Appname.funcname(...)
macro. If you happen to use a non-userpolymake
function in REPL quite often you mightPolymake.@register Appname.funcname
so that it becomes available for completion. This is a purely convenience macro, as the effects of@register
will be lost when Julia kernel restarts. - All big objects of
polymake
can be constructed via@pm
macro. For example
$obj = new BigObject<Template,Parameters>(args)
becomes
obj = @pm Appname.BigObject{Templete,Parameters}(args)
See Section Polymake syntax translation for concrete examples.
- Properties of big objects are accessible by
bigobject.property
syntax (as opposed to$bigobject->property
inpolymake
). If there is a missing property (e.g.Polytope.Polytope
does not haveDIM
property inPolymake.jl
), please check if it can be accessed byAppname.property(object)
. For example propertyDIM
is exposed asPolytope.dim(...)
function. - Methods are available as functions in the appropriate modules, with the first argument as the object, i.e.
$bigobj->methodname(...)
can be called viaAppname.methodname(bigobj, ...)
- A function in
Polymake.jl
callingpolymake
may return a big or small object, and the generic return (PropertyValue
) is transparently converted to one of the data types above. If you really care about performance, this conversion can be deactivated by addingkeep_PropertyValue=true
keyword argument to function/method call.
Functions in Polymake.jl
accept the following types for their arguments:
- simple data types (bools, machine integers, floats)
- wrapped native types (
pm_Integer
,pm_Rational
,pm_Vector
,pm_Matrix
,pm_Set
etc.) - other objects returned by polymake:
pm_perl_Object
(essentially Big Objects),pm_perl_PropertyValue
(containers opaque to Julia)
If an object passed to Polymake.jl
function is of a different type the software will try its best to convert it to such. However, if the conversion doesn't work the ArgumentError
will be thrown:
ERROR: ArgumentError: Unrecognized argument type: SomeType.
You need to convert to polymake compatible type first.
You can tell Polymake.jl
how to convert it by definig
Base.convert(::Type{Polymake.PolymakeType}, ma::SomeType)
The returned value must be of one of the types as above. For example to use AbstractAlgebra
matrices as input to Polymake.jl
one may define
Base.convert(::Type{Polymake.PolymakeType}, M::Generic.MatSpaceElem) = pm_Matrix(M.entries)
and the following should run smoothly.
julia> using AbstractAlgebra, Polymake
polymake version 3.4
Copyright (c) 1997-2019
Ewgenij Gawrilow, Michael Joswig (TU Berlin)
https://polymake.org
This is free software licensed under GPL; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
julia> mm = AbstractAlgebra.matrix(ZZ, [1 2 3; 4 5 6])
[1 2 3]
[4 5 6]
julia> @pm Polytope.Polytope(POINTS=mm)
ERROR: ArgumentError: Unrecognized argument type: AbstractAlgebra.Generic.MatSpaceElem{Int64}.
You need to convert to polymake compatible type first.
[...]
julia> Base.convert(::Type{Polymake.PolymakeType}, M::Generic.MatSpaceElem) = pm_Matrix(M.entries)
julia> @pm Polytope.Polytope(POINTS=mm)
type: Polytope<Rational>
POINTS
1 2 3
1 5/4 3/2