user_guide:tutorials:latest:lattice_polytopes_tutorial

Differences

This shows you the differences between two versions of the page.


user_guide:tutorials:latest:lattice_polytopes_tutorial [2023/11/06 10:57] (current) – created - external edit 127.0.0.1
Line 1: Line 1:
 +====== Tutorial for Lattice Polytopes ======
 +
 +This page gives a small introduction to lattice polytopes in ''%%polymake%%'', some useful external software, and usage hints for it. For a list of methods and properties applicable to lattice polytopes see [[user_guide:lattice_polytopes_doc|here]]. For an introduction to the ''%%polymake%%'' package see [[user_guide:start|here]].
 +
 +''%%polymake%%'' always assumes that the lattice used to define a lattice polytope is the standard lattice Z<sup>d</sup>. Some rules also require that the polytope is full dimensional. There are user functions that transform a polytope sitting in some affine subspace of R<sup>d</sup> into a full dimensional polytope, either in the induced lattice or the lattice spanned by the vertices, see below.
 +
 +===== Dependence on other Software =====
 +
 +For some computations ''%%polymake%%'' has no built-in commands and passes the computation to external software. Currently, polymake has an interface to the following packages that compute various properties of lattice polytopes.
 +
 +  * [[http://www.math.uos.de/normaliz/|libnormaliz]] by Winfried Bruns and Bogdan Ichim, bundled with polymake
 +  * [[http://www.4ti2.de/|4ti2]] by the 4ti2 team
 +  * [[http://www.math.ucdavis.edu/~mkoeppe/latte/|LattE macchiato]] by Matthias Köppe, building on ''%%LattE%%'' by Jesus de Loera et. al.
 +  * ([[http://freshmeat.net/projects/barvinok|barvinok]] by Sven Verdoolaege)
 +
 +Unless you want to deal with Hilbert bases of cones you don't need them. If you do, either the bundled extension ''%%libnormaliz%%'' or the external package ''%%4ti2%%'' suffices to do most computations with lattice polytopes. Computation of Gröbner bases currently requires ''%%4ti2%%''. ''%%LattE%%'' only counts lattice points in a polytope and computes its Ehrhart polynomial, but may be faster on that than any other methods implemented. ''%%barvinok%%'' can be used to compute the number of lattice points and the h-polynomial. Access to barvinok is realized via an extension which has to be downloaded separately.
 +
 +For some of the commands in this tutorial you will need at least one of ''%%bundled:libnormaliz%%'' enabled or ''%%4ti2%%'' installed on your machine. We'll remind you at the relevant places.
 +
 +===== Lattice Points in Rational Polytopes =====
 +
 +We start by creating a rational polytope using one of ''%%polymake%%'''s standard polytope constructions. We choose the 3-dimensional cube with coordinates +1 and -1. So we start ''%%polymake%%'' at the command line and assign a cube to the variable $p.
 +
 +<code perl>
 +> $p=cube(3);
 +</code>
 +Suppose we want to know how many lattice points this cube contains. The answer is of course already known, as the cube has one relative interior integral point per non-empty face. So we expect to get the answer 27.
 +
 +<code perl>
 +> print $p->N_LATTICE_POINTS;
 +27
 +</code>
 +To satisfy this request, ''%%polymake%%'' computes all properties necessary to call an external program that provides the number of lattice points. In this case, ''%%polymake%%'' has passed the request to ''%%lattE%%'', which is shown by the credit message that appears before the answer. By default, credits for external software are shown when an external package is used for the first time. You can change this behavior using the variable ''%%$Verbose::credits%%''. If you don't have a version of ''%%LattE%%'', or if you have set different preferences, then ''%%polymake%%'' may choose one of the other programs. So the credit statement depends on your configuration.
 +
 +We can of course also ask ''%%polymake%%'' to compute the integral points for us. For our next computations we are only interested in the integral points in the interior of the cube, so we ask for
 +
 +<code perl>
 +> print $p->INTERIOR_LATTICE_POINTS;
 +1 0 0 0
 +</code>
 +Internally, ''%%polymake%%'' computes the intersection of the polytope with the integer lattice, and then checks which of the points lies on a facet of $p. By default, ''%%polymake%%'' uses a project-and-lift algorithms to enumerate the lattice points. Note that our call to ''%%LattE%%'' above has only computed the number of integral points (which is done with an improved version of Barvinok's algorithm), so ''%%polymake%%'' really has to compute something here. If we had asked for ''%%INTERIOR_LATTICE_POINTS%%'' first, then ''%%N_LATTICE_POINTS%%'' would just have counted the rows of a matrix, which would have been much faster. So computation time can depend on the history.
 +
 +You can also ask for the HILBERT_BASIS, though in the case of a cube the result is not so exciting:
 +
 +<code perl>
 +> print $p->HILBERT_BASIS;
 +1 -1 -1 -1
 +1 -1 -1 0
 +1 -1 -1 1
 +1 -1 0 -1
 +1 -1 0 0
 +1 -1 0 1
 +1 -1 1 -1
 +1 -1 1 0
 +1 -1 1 1
 +1 0 -1 -1
 +1 0 -1 0
 +1 0 -1 1
 +1 0 0 -1
 +1 0 0 0
 +1 0 0 1
 +1 0 1 -1
 +1 0 1 0
 +1 0 1 1
 +1 1 -1 -1
 +1 1 -1 0
 +1 1 -1 1
 +1 1 0 -1
 +1 1 0 0
 +1 1 0 1
 +1 1 1 -1
 +1 1 1 0
 +1 1 1 1
 +</code>
 +''%%polymake%%'' has no native method to compute a Hilbert basis, so it has passed the computation to ''%%4ti2%%''. The choice may vary, depending on what is installed on your computer (and configured for ''%%polymake%%''). You can influence the choice with the appropriate ''%%prefer%%'' statement.
 +
 +Note that so far these commands also work for rational polytopes.
 +
 +===== Lattice Polytopes =====
 +
 +Now we want to do some computations that don't make sense for polytopes that have non-integral vertex coordinates. We can let ''%%polymake%%'' check that our cube is indeed a polytope with integral vertices.
 +
 +<code perl>
 +> print $p->LATTICE;
 +true
 +</code>
 +A particularly interesting class of lattice polytopes is that of reflexive polytopes. A polytope is //reflexive// if its polar is agein alattice polytope. This implies in particular that the origin is the unique interior lattice point in the polytope. So, as we have seen above, our cube is a candidate. But this is not sufficient, so we have to do further checks.
 +
 +Reflexivity is a property that is not defined for polytopes with non-integral vertices. So if we ask for it in ''%%polymake%%'', then ''%%polymake%%'' checks that the entered polytope is indeed a lattice polytope (i.e. it is **bounded** and has **integral vertices**). In that case the object will automatically get the specialization ''%%Polytope::Lattice%%''.
 +
 +<code perl>
 +> print $p->REFLEXIVE;
 +true
 +</code>
 +Lattice polytopes can be used to define toric varieties with an ample line bundle, and many properties of the variety are reflected by the polytope. here is an example: The toric variety defined by our cube is //smooth//, i.e. it is one of the //smooth toric Fano varieties//. In ''%%polymake%%'', we can just ask for this property in the following way.
 +
 +<code perl>
 +> print $p->SMOOTH;
 +true
 +</code>
 +The number of integral points in the k-th dilate of a polytope is given by a polynomial of degree d in k. This is the famous //Ehrhart Theorem//. In ''%%polymake%%'' you can obtain the coefficients of this polynomial (starting with the constant coefficient).
 +
 +<code perl>
 +> print $p->EHRHART_POLYNOMIAL;
 +8*x^3 + 12*x^2 + 6*x + 1
 +</code>
 +''%%polymake%%'' has passed this request to ''%%LattE%%'' or ''%%normaliz%%'', but as we have used these programs already the credit message is suppressed (but if you save the cube to a file, then you will find it in there). Some coefficients of this polynomial have a geometric interpretation. E.g., the highest coefficient is the Euclidean volume of the polytope.
 +
 +<code perl>
 +> print $p->VOLUME;
 +8
 +</code>
 +By a theorem of Stanley, the generating function for the number of lattice points can be written as the quotient of a polynomial h<sup></sup>(t) by (1-t)<sup>d+1</sup>, and this polynomial has non-negative integral coefficients.
 +
 +<code perl>
 +> print $p->H_STAR_VECTOR;
 +1 23 23 1
 +> print $p->LATTICE_DEGREE;
 +3
 +> print $p->LATTICE_CODEGREE;
 +1
 +</code>
 +In our case the coefficient vector is symmetric, as the polytope is reflexive. The //co-degree// of the polytope is d+1 minus the degree of the h<sup></sup>-polynomial. It is the smallest factor by which we have to dilate the polytope to obtain an interior integral point. In our case, this is 1, as the cube already has an integral point.
 +
 +We can obtain the volume of our polytope also from the ''%%H_STAR_VECTOR%%'': Summing up the coefficients give the //lattice volume// of the polytope, which is d! times its Euclidean volume.
 +
 +<code perl>
 +> print $p->LATTICE_VOLUME;
 +48
 +</code>
 +Let us look at a different example:
 +
 +<code perl>
 +> $q=new Polytope(INEQUALITIES=>[[5,-4,0,1],[-3,0,-4,1],[-2,1,0,0],[-4,4,4,-1],[0,0,1,0],[8,0,0,-1],[1,0,-1,0],[3,-1,0,0]]);
 +</code>
 +This actually defines a lattice polytope, which we can see from the list of vertices:
 +
 +<code perl>
 +> print $q->VERTICES;
 +1 3 1 7
 +1 2 0 3
 +1 3 0 7
 +1 2 1 7
 +1 2 0 4
 +1 3 1 8
 +1 3 0 8
 +1 2 1 8
 +</code>
 +''%%polymake%%'' provides basically three methods for convex hull conversion, double description, reverse search, and beneath beyond. The first two are provided by the packages ''%%cdd%%'' and ''%%lrs%%'', the last in internal. By default, ''%%cdd%%'' is chosen, and that is what was used above (they are bundled with ''%%polymake%%'', you don't have to install them). A polytope Q is //normal// if every lattice point in the k-th dilate of Q is the sum of k lattice points in Q. You can check this property via
 +
 +<code perl>
 +> print $q->NORMAL;
 +false
 +</code>
 +So our polytope is not normal. We can also find a point that violates the condition. Being normal is equivalent to the fact, that the Hilbert basis of the cone C(Q) obtained from Q by embedding the polytope at height one and the coning over it has all its generators in height one. The property HILBERT_BASIS computes these generators:
 +
 +<code perl>
 +> print $q->HILBERT_BASIS;
 +1 2 0 3
 +1 2 0 4
 +1 2 1 7
 +1 2 1 8
 +1 3 0 7
 +1 3 0 8
 +1 3 1 7
 +1 3 1 8
 +2 5 1 13
 +</code>
 +The last row is the desired vector: [2,5,1,13] is a vector in 2*Q, but it is not a sum of lattice points in Q. The cone C(Q) corresponds to an affine toric variety, and the above tells us that this variety is not normal. Yet, it is very ample, as we can check with
 +
 +<code perl>
 +> print $q->VERY_AMPLE;
 +true
 +</code>
 +Now assume we are particularly interested in the third facet of Q. We can pick this via
 +
 +<code perl>
 +> $f=facet($q,2);
 +</code>
 +Recall that indexes in ''%%polymake%%'' start at 0, so the third facet has index 2. This is again a very ample polytope:
 +
 +<code perl>
 +> print $f->VERY_AMPLE;
 +true
 +</code>
 +The result is no surprise, being very ample is inherited by faces. We could also be interested in the facet width of the polytope ''%%$f%%''. This is the minimum over the maximal distance of a facet to any other vertex. ''%%polymake%%'' knows how to compute this:
 +
 +<code perl>
 +> #print $f->FACET_WIDTH;
 +</code>
 +Almost. It tells you that it can only do this for a full dimensional polytope, i.e. for a polytope whose dimension coincides with the ambient dimension. This is not true for our facet: It lives in the same ambient space as ''%%$q%%'', but has one dimension less. We can remedy this by applying the following:
 +
 +<code perl>
 +> $g=ambient_lattice_normalization($f);
 +> print $g->FACET_WIDTH;
 +1
 +</code>
 +The function ''%%ambient_lattice_normalization%%'' returns a full dimensional version of the polytope ''%%$f%%'' in the lattice induced by the intersection of the affine space of ''%%$f%%'' with Z^n. Now ''%%$g%%'' is full dimensional, and we can compute the facet width. Note that there is also a function which normalizes in the lattice spanned by the vertices of the polytope: ''%%vertex_facet_normalization%%''. This can also be usefull for full dimensional polytopes. E.g. consider the cube we defined above. The sum of the entries of each vertex is odd, so the lattice spannd by the vertices is a sublattice of the integer lattice:
 +
 +<code perl>
 +> $cr=vertex_lattice_normalization($p);
 +> print $cr->VERTICES;
 +(4) (0 1)
 +1 1 0 0
 +1 0 1 0
 +1 1 1 0
 +1 0 0 1
 +1 1 0 1
 +1 0 1 1
 +1 1 1 1
 +</code>
 +''%%$cr%%'' is the same cube, but we have reduced the lattice. (The first line is a //sparse representation// of a vector: it has length 4, and the only non-zero entry is at position 0 and is 1 (note that indexes start at 0)).
 +
 +===== Toric Varieties =====
 +
 +''%%polymake%%'' has only few builtin functions to compute properties of the variety associated to a fan or lattice polytope. There are two extensions available that add more properties, both currently at an early stage:
 +
 +  * [[https://github.com/lkastner|Toric Varieties and Singular interface]] by Lars Kastner/Benjamin Lorenz
 +  * [[http://www.mathematik.tu-darmstadt.de/~paffenholz/software.html|ToricVarieties-v0.3]] by Andreas Paffenholz. Defines a new property for toric varieties associated to a fan and divisors on that variety.
 +
 +Here we will do some computations that do not require one of the extensions. We start by defining a fan. We'll make our live easy and take the normal fan of our cube:
 +
 +<code perl>
 +> application "fan";
 +> $f = normal_fan($p);
 +> print $f->SMOOTH_FAN;
 +true
 +</code>
 +With the last line we have verified that our fan defines a smooth toric variety. Note that switching the application is not strictly necessary, you can also prepend calls to functions and constructors with ''%%fan::%%''. The fan object ''%%$f%%'' itself knows its type, and chooses available properties based on this. Any smooth variety is Gorenstein, so we expect the following:
 +
 +<code perl>
 +> print $f->GORENSTEIN;
 +true
 +</code>
 +Similarly, we could check for Q-Gorensteinness with ''%%Q_GORENSTEIN%%''. It is also a complete fan:
 +
 +<code perl>
 +> print $f->COMPLETE;
 +true
 +</code>
 +but currently there is little support to detect completeness in ''%%polymake%%''. In our case it was already decided during construction, normal fans are complete. You can also check standard features of fans, like their rays. Let us do this for the normal fan of our other example:
 +
 +<code perl>
 +> $g=normal_fan($q);
 +> print $g->RAYS;
 +-1 0 1/4
 +0 -1 1/4
 +1 0 0
 +1 1 -1/4
 +0 1 0
 +0 0 -1
 +0 -1 0
 +-1 0 0
 +</code>
 +This is not what we wanted. We would like to see the minimal lattice generators of the rays. We can fix this using
 +
 +<code perl>
 +> print primitive($g->RAYS);
 +-4 0 1
 +0 -4 1
 +1 0 0
 +4 4 -1
 +0 1 0
 +0 0 -1
 +0 -1 0
 +-1 0 0
 +</code>
 +Note that the function ''%%primitive%%'' returns a copy of the argument, the RAYS as stored in the fan are unchanged. So you have to apply this function each time you need the primitive generators, or you store them in a new variable. The fan $g$ is not smooth, but still Gorenstein:
 +
 +<code perl>
 +> print $g->SMOOTH_FAN;
 +false
 +> print $g->GORENSTEIN;
 +true
 +</code>
 +You can also access the maximal cones of the fan via
 +
 +<code perl>
 +> print $g->MAXIMAL_CONES;
 +{0 1 6 7}
 +{0 1 2 4}
 +{0 4 7}
 +{1 2 6}
 +{2 3 4}
 +{5 6 7}
 +{3 4 5 7}
 +{2 3 5 6}
 +</code>
 +The indices in these list refer to the list of rays. Sometimes you might be interested in the walls, i.e. the codimension 2 faces of the fan. Here is one way to get them
 +
 +<code perl>
 +> print rows_numbered($g->HASSE_DIAGRAM->FACES);
 +0:-1
 +1:0 1 6 7
 +2:0 1 2 4
 +3:0 4 7
 +4:1 2 6
 +5:2 3 4
 +6:5 6 7
 +7:3 4 5 7
 +8:2 3 5 6
 +9:0 1
 +10:0 7
 +11:1 6
 +12:6 7
 +13:0 4
 +14:1 2
 +15:2 4
 +16:4 7
 +17:2 6
 +18:3 4
 +19:2 3
 +20:5 7
 +21:5 6
 +22:3 5
 +23:0
 +24:1
 +25:7
 +26:6
 +27:4
 +28:2
 +29:3
 +30:5
 +31:
 +> print $g->HASSE_DIAGRAM->nodes_of_dim($g->DIM-2);
 +{23 24 25 26 27 28 29 30}
 +</code>
 +where the list of numbers given by the latter are the indices of the codimension 2 faces in the list of all faces given before. There is a more concise way to list those, using some simple perl programming:
 +
 +<code perl>
 +> print map($g->HASSE_DIAGRAM->FACES->[$_], @{$g->HASSE_DIAGRAM->nodes_of_dim($g->DIM-2)});
 +{0}{1}{7}{6}{4}{2}{3}{5}
 +</code>
 +===== Visualization =====
 +
 +If the lattice polytope lives in R^2 or R^3, then we can visualize the polytope together with its lattice points.
 +
 +<code perl>
 +> $p->VISUAL->LATTICE_COLORED;
 +</code>
 +<HTML>
 +<!--
 +polymake for knusper
 +Thu Mar  3 00:37:13 2022
 +p
 +-->
 +
 +
 +<html>
 +   <head>
 +      <meta charset=utf-8>
 +      <title>p</title>
 +      <style>
 +/*
 +// COMMON_CODE_BLOCK_BEGIN
 +*/
 +         html {overflow: scroll;}
 +         strong{font-size: 18px;}
 +         canvas { z-index: 8; }
 +         input[type='radio'] {margin-left:0;}
 +         input[type='checkbox'] {margin-right:7px; margin-left: 0px; padding-left:0px;}
 +         .group{padding-bottom: 15px;}
 +         .settings * {z-index: 11; }
 +         .settings{z-index: 10; font-family: Arial, Helvetica, sans-serif; margin-left: 30px; visibility: hidden; width: 14em; height: 96%; border: solid 1px silver; padding: 2px; overflow-y: scroll; box-sizing: border-box; background-color: white; position: absolute;}
 +         .indented{margin-left: 20px; margin-top: 10px; padding-bottom: 0px;} 
 +         .shownObjectsList{overflow: auto; max-width: 150px; max-height: 150px;}
 +         .showSettingsButton{visibility: visible; z-index: 12; position: absolute }
 +         .hideSettingsButton{visibility: hidden; z-index: 12; position: absolute; opacity: 0.5}
 +         button{margin-left: 0; margin-top: 10px}
 +         img{cursor: pointer;}
 +         .suboption{padding-top: 15px;}
 +         #model15249231559 { width: 100%; height: 100%; }
 +         .threejs_container { width: 100%; height: 75vh;}
 +         .settings{max-height: 74vh} 
 +         input[type=range] {
 +           -webkit-appearance: none;
 +           padding:0; 
 +           width:90%; 
 +           margin-left: auto;
 +           margin-right: auto;
 +           margin-top: 15px;
 +           margin-bottom: 15px;
 +           display: block;
 +         }
 +         input[type=range]:focus {
 +           outline: none;
 +         }
 +         input[type=range]::-webkit-slider-runnable-track {
 +           height: 4px;
 +           cursor: pointer;
 +           animate: 0.2s;
 +           box-shadow: 0px 0px 0px #000000;
 +           background: #E3E3E3;
 +           border-radius: 0px;
 +           border: 0px solid #000000;
 +         }
 +         input[type=range]::-webkit-slider-thumb {
 +           box-shadow: 1px 1px 2px #B8B8B8;
 +           border: 1px solid #ABABAB;
 +           height: 13px;
 +           width: 25px;
 +           border-radius: 20px;
 +           background: #E0E0E0;
 +           cursor: pointer;
 +           -webkit-appearance: none;
 +           margin-top: -5px;
 +         }
 +         input[type=range]:focus::-webkit-slider-runnable-track {
 +           background: #E3E3E3;
 +         }
 +         input[type=range]::-moz-range-track {
 +           height: 4px;
 +           cursor: pointer;
 +           animate: 0.2s;
 +           box-shadow: 0px 0px 0px #000000;
 +           background: #E3E3E3;
 +           border-radius: 0px;
 +           border: 0px solid #000000;
 +         }
 +         input[type=range]::-moz-range-thumb {
 +           box-shadow: 1px 1px 2px #B8B8B8;
 +           border: 1px solid #ABABAB;
 +           height: 13px;
 +           width: 25px;
 +           border-radius: 20px;
 +           background: #E0E0E0;
 +           cursor: pointer;
 +         }
 +         input[type=range]::-ms-track {
 +           height: 4px;
 +           cursor: pointer;
 +           animate: 0.2s;
 +           background: transparent;
 +           border-color: transparent;
 +           color: transparent;
 +         }
 +         input[type=range]::-ms-fill-lower {
 +           background: #E3E3E3;
 +           border: 0px solid #000000;
 +           border-radius: 0px;
 +           box-shadow: 0px 0px 0px #000000;
 +         }
 +         input[type=range]::-ms-fill-upper {
 +           background: #E3E3E3;
 +           border: 0px solid #000000;
 +           border-radius: 0px;
 +           box-shadow: 0px 0px 0px #000000;
 +         }
 +         input[type=range]::-ms-thumb {
 +           box-shadow: 1px 1px 2px #B8B8B8;
 +           border: 1px solid #ABABAB;
 +           height: 13px;
 +           width: 25px;
 +           border-radius: 20px;
 +           background: #E0E0E0;
 +           cursor: pointer;
 +         }
 +         input[type=range]:focus::-ms-fill-lower {
 +           background: #E3E3E3;
 +         }
 +         input[type=range]:focus::-ms-fill-upper {
 +           background: #E3E3E3;
 +         }
 +/*
 +// COMMON_CODE_BLOCK_END
 +*/
 + </style>
 +   </head>
 +<body>
 +   <div class='threejs_container'>
 + <div id='settings_0' class='settings'>
 + <div class=group id='explode_0'>
 + <strong>Explode</strong>
 + <input id='explodeRange_0' type='range' min='0.00001' max=6 step=0.01 value=0.00001>
 + <div class=indented><input id='explodeCheckbox_0' type='checkbox'>Automatic explosion</div>
 + <div class=suboption>Exploding speed</div>
 + <input id='explodingSpeedRange_0' type='range' min=0 max=0.5 step=0.001 value=0.05>
 + </div>
 +
 + <div class=group id='transparency_0' class='transparency'>
 + <strong>Transparency</strong>
 + <input id='transparencyRange_0' type='range' min=0 max=1 step=0.01 value=0>
 +            <div class=indented><input id='depthWriteCheckbox_0' type='checkbox'>depthWrite</div>
 + </div>
 +
 + <div class=group id='rotation_0'>
 + <strong>Rotation</strong>
 + <div class=indented>
 + <div><input type='checkbox' id='changeRotationX_0'> x-axis</div>
 + <div><input type='checkbox' id='changeRotationY_0'> y-axis</div>
 + <div><input type='checkbox' id='changeRotationZ_0'> z-axis</div>
 + <button id='resetButton_0'>Reset</button>
 + </div>
 +
 + <div class=suboption>Rotation speed</div>
 + <input id='rotationSpeedRange_0' type='range' min=0 max=5 step=0.01 value=2>
 + </div>
 +
 +
 + <div class=group id='display_0'>
 + <strong>Display</strong>
 + <div class=indented>
 + <div id='shownObjectTypesList_0' class='shownObjectsList'></div>
 + </div>
 + <div class=suboption>Objects</div>
 + <div class=indented>
 +    <div id='shownObjectsList_0' class='shownObjectsList'></div>
 + </div>
 + </div>
 +         
 +         <div class=group id='camera_0'>
 +            <strong>Camera</strong>
 +            <div class=indented>
 +               <form>
 +                  <select id="cameraType_0">
 +                     <option value='perspective' selected> Perspective<br></option>
 +                     <option value='orthographic' > Orthographic<br></option>
 +                  </select>
 +               </form>
 +            </div>
 +         </div>
 +
 + <div class=group id='svg_0'>
 + <strong>SVG</strong>
 + <div class=indented>
 + <form>
 + <input type="radio" name='screenshotMode' value='download' id='download_0' checked> Download<br>
 + <input type="radio" name='screenshotMode' value='tab' id='tab_0' > New tab<br>
 + </form>
 + <button id='takeScreenshot_0'>Screenshot</button>
 + </div>
 + </div>
 +
 + </div> <!-- end of settings -->
 + <img id='hideSettingsButton_0' class='hideSettingsButton' src='/kernelspecs/r118/polymake/close.svg' width=20px">
 + <img id='showSettingsButton_0' class='showSettingsButton' src='/kernelspecs/r118/polymake/menu.svg' width=20px">
 +<div id="model15249231559"></div>
 +</div>
 +   <script>
 +    requirejs.config({
 +      paths: {
 +        three: '/kernelspecs/r118/polymake/three',
 +        TrackballControls: '/kernelspecs/r118/polymake/TrackballControls',
 +        OrbitControls: '/kernelspecs/r118/polymake/OrbitControls',
 +        Projector: '/kernelspecs/r118/polymake/Projector',
 +        SVGRenderer: '/kernelspecs/r118/polymake/SVGRenderer',
 +        WEBGL: '/kernelspecs/r118/polymake/WebGL',
 +      },
 +      shim: {
 +        'three': { exports: 'THREE'},
 +        'SVGRenderer': { deps: [ 'three' ], exports: 'THREE.SVGRenderer' },
 +        'WEBGL': { deps: [ 'three' ], exports: 'THREE.WEBGL' },
 +        'Projector': { deps: [ 'three' ], exports: 'THREE.Projector' },
 +        'TrackballControls': { deps: [ 'three' ], exports: 'THREE.TrackballControls' },
 +        'OrbitControls': { deps: [ 'three' ], exports: 'THREE.OrbitControls' },
 +      }
 +    });
 +    
 +    require(['three'],function(THREE){
 +        window.THREE = THREE;
 +      require(['TrackballControls', 'OrbitControls', 'Projector', 'SVGRenderer', 'WEBGL'],
 +               function(TrackballControls, OrbitControls, Projector, SVGRenderer, WEBGL) {
 +    THREE.TrackballControls = TrackballControls;
 +    THREE.OrbitControls = OrbitControls;
 +    THREE.Projector = Projector;
 +    THREE.SVGRenderer = SVGRenderer;
 +    THREE.WEBGL = WEBGL;
 +
 +// COMMON_CODE_BLOCK_BEGIN
 +
 +const intervalLength = 25; // for automatic animations
 +const explodableModel = true; 
 +const modelContains = { points: false, pointlabels: false, lines: false, edgelabels: false, faces: false, arrowheads: false };
 +const foldables = [];
 +
 +var three = document.getElementById("model15249231559");
 +var scene = new THREE.Scene();
 +var renderer = new THREE.WebGLRenderer( { antialias: true } );
 +var svgRenderer = new THREE.SVGRenderer( { antialias: true } );
 +renderer.setPixelRatio( window.devicePixelRatio );
 +renderer.setClearColor(0xFFFFFF, 1);
 +svgRenderer.setClearColor(0xFFFFFF, 1);
 +three.appendChild(renderer.domElement);
 +
 +var frustumSize = 4;
 +var cameras = [new THREE.PerspectiveCamera(75, 1, 0.1, 1000), new THREE.OrthographicCamera()];
 +cameras.forEach(function(cam) {
 +    cam.position.set(0, 0, 5);
 +    cam.lookAt(0, 0, 0);  
 +    cam.up.set(0, 1, 0);         
 +});
 +var controls = [new THREE.TrackballControls(cameras[0], three), new THREE.OrbitControls(cameras[1], three)];
 +var camera, control;
 +
 +controls[0].zoomSpeed = 0.2;
 +controls[0].rotateSpeed = 4;
 +
 +
 +// class to allow move points together with labels and spheres
 +var PMPoint = function (x,y,z) {
 +   this.vector = new THREE.Vector3(x,y,z);
 +   this.sprite = null;
 +   this.sphere = null;
 +}
 +PMPoint.prototype.addLabel = function(labelsprite) {
 +   this.sprite = labelsprite;
 +   this.sprite.position.copy(this.vector);
 +}
 +PMPoint.prototype.addSphere = function(spheremesh) {
 +   this.sphere = spheremesh;
 +   this.sphere.position.copy(this.vector);
 +}
 +PMPoint.prototype.set = function(x,y,z) {
 +   this.vector.set(x,y,z);
 +   if (this.sprite) {
 +      this.sprite.position.copy(this.vector);
 +   }
 +   if (this.sphere) {
 +      this.sphere.position.copy(this.vector);
 +   }
 +}
 +PMPoint.prototype.radius = function() {
 +   if (this.sphere) {
 +      return this.sphere.geometry.parameters.radius;
 +   } else {
 +      return 0;
 +   }
 +};
 +// select the target node
 +var target = document.querySelector('#model15249231559');
 +
 +// create an observer instance
 +var observer = new MutationObserver(function(mutations) {
 +   mutations.forEach(function(mutation) {
 +      if (mutation.removedNodes && mutation.removedNodes.length > 0) {
 +         cancelAnimationFrame(renderId);
 +         observer.disconnect();
 +         console.log("cancelled frame "+renderId);
 +      }
 +   });
 +});
 +
 +// configuration of the observer:
 +var config = { childList: true, characterData: true }
 +
 +// pass in the target node, as well as the observer options
 +while (target) {
 +   if (target.className=="output") {
 +      observer.observe(target, config);
 +      break;
 +   }
 +   target = target.parentNode;
 +}
 +
 +// COMMON_CODE_BLOCK_END
 +
 +var obj0 = new THREE.Object3D();
 +obj0.name = "p";
 +obj0.userData.explodable = 1;
 +obj0.userData.points = [];
 +obj0.userData.points.push(new PMPoint(-1, -1, -1));
 +obj0.userData.points.push(new PMPoint(1, -1, -1));
 +obj0.userData.points.push(new PMPoint(-1, 1, -1));
 +obj0.userData.points.push(new PMPoint(1, 1, -1));
 +obj0.userData.points.push(new PMPoint(-1, -1, 1));
 +obj0.userData.points.push(new PMPoint(1, -1, 1));
 +obj0.userData.points.push(new PMPoint(-1, 1, 1));
 +obj0.userData.points.push(new PMPoint(1, 1, 1));
 +
 +obj0.userData.edgeindices = [0, 1, 0, 2, 1, 3, 2, 3, 0, 4, 1, 5, 4, 5, 2, 6, 4, 6, 3, 7, 5, 7, 6, 7];
 +   <!-- Edge style -->
 +obj0.userData.edgematerial = new THREE.LineBasicMaterial( { color: 0x000000, linewidth: 1.5, transparent: false } );
 +obj0.userData.facets = [[0, 4, 6, 2], [7, 5, 1, 3], [5, 4, 0, 1], [2, 6, 7, 3], [0, 2, 3, 1], [6, 4, 5, 7]];
 +init_object(obj0);
 +scene.add(obj0);
 +
 +var obj1 = new THREE.Object3D();
 +obj1.name = "Lattice points and vertices of p";
 +obj1.userData.explodable = 1;
 +obj1.userData.points = [];
 +obj1.userData.points.push(new PMPoint(0, 0, 0));
 +obj1.userData.points.push(new PMPoint(-1, -1, -1));
 +obj1.userData.points.push(new PMPoint(-1, -1, 0));
 +obj1.userData.points.push(new PMPoint(-1, -1, 1));
 +obj1.userData.points.push(new PMPoint(-1, 0, -1));
 +obj1.userData.points.push(new PMPoint(-1, 0, 0));
 +obj1.userData.points.push(new PMPoint(-1, 0, 1));
 +obj1.userData.points.push(new PMPoint(-1, 1, -1));
 +obj1.userData.points.push(new PMPoint(-1, 1, 0));
 +obj1.userData.points.push(new PMPoint(-1, 1, 1));
 +obj1.userData.points.push(new PMPoint(0, -1, -1));
 +obj1.userData.points.push(new PMPoint(0, -1, 0));
 +obj1.userData.points.push(new PMPoint(0, -1, 1));
 +obj1.userData.points.push(new PMPoint(0, 0, -1));
 +obj1.userData.points.push(new PMPoint(0, 0, 1));
 +obj1.userData.points.push(new PMPoint(0, 1, -1));
 +obj1.userData.points.push(new PMPoint(0, 1, 0));
 +obj1.userData.points.push(new PMPoint(0, 1, 1));
 +obj1.userData.points.push(new PMPoint(1, -1, -1));
 +obj1.userData.points.push(new PMPoint(1, -1, 0));
 +obj1.userData.points.push(new PMPoint(1, -1, 1));
 +obj1.userData.points.push(new PMPoint(1, 0, -1));
 +obj1.userData.points.push(new PMPoint(1, 0, 0));
 +obj1.userData.points.push(new PMPoint(1, 0, 1));
 +obj1.userData.points.push(new PMPoint(1, 1, -1));
 +obj1.userData.points.push(new PMPoint(1, 1, 0));
 +obj1.userData.points.push(new PMPoint(1, 1, 1));
 +
 +obj1.userData.pointradii = 0.02;
 +   <!-- Vertex style -->
 +obj1.userData.pointmaterial = [new THREE.MeshBasicMaterial( { color: 0x1EFA1E, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } ),
 +new THREE.MeshBasicMaterial( { color: 0x469646, side: THREE.DoubleSide, transparent: false } )];
 +init_object(obj1);
 +scene.add(obj1);
 +
 +// COMMON_CODE_BLOCK_BEGIN
 +function textSpriteMaterial(message, parameters) {
 +    if ( parameters === undefined ) parameters = {};
 +    var fontface = "Helvetica";
 +    var fontsize = parameters.hasOwnProperty("fontsize") ? parameters["fontsize"] : 15;
 +    fontsize = fontsize*10;
 +    var lines = message.split('\\n');
 +    var size = 512;
 +    for(var i = 0; i<lines.length; i++){
 +        var tmp = lines[i].length;
 +        while(tmp*fontsize > size){
 +           fontsize--;
 +        }
 +    }
 +    
 +    var canvas = document.createElement('canvas');
 +    canvas.width = size;
 +    canvas.height = size;
 +    var context = canvas.getContext('2d');
 +    context.fillStyle = "rgba(255, 255, 255, 0)";
 +    context.fill();
 +    context.font = fontsize + "px " + fontface;
 +    
 +    // text color
 +    context.fillStyle = "rgba(0, 0, 0, 1.0)";
 +     for(var i = 0; i<lines.length; i++){
 +        context.fillText(lines[i], size/2, size/2+i*fontsize);
 +     }
 +    
 +    // canvas contents will be used for a texture
 +    var texture = new THREE.Texture(canvas);
 +    texture.needsUpdate = true;
 +    
 +    var spriteMaterial = new THREE.SpriteMaterial({map: texture, depthTest: true, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -1, polygonOffsetUnits: 1 });
 +    return spriteMaterial;
 +}
 +
 +
 +// ---------------------- INITIALIZING OBJECTS--------------------------------------
 +// ---------------------------------------------------------------------------------
 +
 +function init_object(obj) {
 +    if (obj.userData.hasOwnProperty("pointmaterial")) {
 +        init_points(obj);
 +        modelContains.points = true;
 +    }
 +    if (obj.userData.hasOwnProperty("pointlabels")) {
 +        init_pointlabels(obj);
 +        modelContains.pointlabels = true;
 +    }
 +    if (obj.userData.hasOwnProperty("edgematerial")) {
 +        init_lines(obj);
 +        modelContains.lines = true;
 +    }
 +    if (obj.userData.hasOwnProperty("edgelabels")) {
 +        init_edgelabels(obj);
 +        modelContains.edgelabels = true;
 +    }
 +    if (obj.userData.hasOwnProperty("arrowstyle")) {
 +        init_arrowheads(obj);
 +        modelContains.arrowheads = true;
 +    }
 +    if (obj.userData.hasOwnProperty("facetmaterial")) {
 +        init_faces(obj);
 +        modelContains.faces = true;
 +    }
 +}
 +
 +function init_points(obj) {
 +    var pointgroup = new THREE.Group();
 +    pointgroup.name = "points";
 +    var points = obj.userData.points;
 +    var radii = obj.userData.pointradii;
 +    var materials = obj.userData.pointmaterial;
 +    var geometry,material;
 +    if (!Array.isArray(radii)) {
 +        geometry = new THREE.SphereBufferGeometry(radii);  
 +    }
 +    if (!Array.isArray(materials)) {
 +        material = materials;
 +    }
 +    for (var i=0; i<points.length; i++) {
 +        var point = points[i];
 +        if (Array.isArray(radii)) {
 +            if (radii[i] == 0) {
 +                continue;
 +            }
 +            geometry = new THREE.SphereBufferGeometry(radii[i]);  
 +        } 
 +        if (Array.isArray(materials)) {
 +            material = materials[i];     
 +        } 
 +        var sphere = new THREE.Mesh(geometry, material);
 +        point.addSphere(sphere);
 +        pointgroup.add(sphere);
 +    }
 +    obj.add(pointgroup);
 +}
 +
 +function init_pointlabels(obj) {
 +    var points = obj.userData.points;
 +    var labels = obj.userData.pointlabels;
 +    var pointlabels = new THREE.Group();
 +    pointlabels.name = "pointlabels";
 +    if (Array.isArray(labels)) {
 +        for (var i=0; i<points.length; i++) {
 +            var point = points[i];
 +            var spriteMaterial = textSpriteMaterial( labels[i] );
 +         var sprite = new THREE.Sprite(spriteMaterial);
 +            point.addLabel(sprite);
 +            pointlabels.add(sprite);
 +        }
 +    } else {
 +        var spriteMaterial = textSpriteMaterial( labels );
 +        for (var i=0; i<points.length; i++) {
 +            var point = points[i];
 +         var sprite = new THREE.Sprite(spriteMaterial);
 +            point.addLabel(sprite);
 +            pointlabels.add(sprite);
 +        }
 +    }
 +    obj.add(pointlabels);
 +}
 +
 +function init_lines(obj) {
 +    var edgeindices = obj.userData.edgeindices;
 +    var points = obj.userData.points;
 +    var materials = obj.userData.edgematerial;
 +    var geometry = new THREE.BufferGeometry();
 +    var bufarr = new Float32Array( obj.userData.edgeindices.length * 3 );
 +    var bufattr = new THREE.Float32BufferAttribute( bufarr, 3 );
 +    var geometry = new THREE.BufferGeometry();
 +    geometry.setAttribute('position', bufattr);
 +    if (Array.isArray(materials)) {     
 +        for (var i=0; i<materials.length; i++) {
 +            geometry.addGroup(2*i,2,i);
 +        }
 +    }
 +    var lines = new THREE.LineSegments(geometry, materials);
 +    lines.name = "lines";
 +    obj.add(lines);
 +    updateEdgesPosition(obj);
 +}
 +
 +function init_edgelabels(obj) {
 +    var points = obj.userData.points;
 +    var edgeindices = obj.userData.edgeindices;
 +    var labels = obj.userData.edgelabels;
 +    var edgelabels = new THREE.Group();
 +    edgelabels.name = "edgelabels";
 +    if (Array.isArray(labels)) {
 +        for (var i=0; i<edgeindices.length/2; i++) {
 +            var spriteMaterial = textSpriteMaterial( labels[i] );
 +            var sprite = new THREE.Sprite(spriteMaterial);
 +            sprite.position.copy(new THREE.Vector3().addVectors(points[edgeindices[2*i]].vector,points[edgeindices[2*i+1]].vector).multiplyScalar(0.5));
 +            edgelabels.add(sprite);
 +        }
 +    } else {
 +        var spriteMaterial = textSpriteMaterial( labels );
 +        for (var i=0; i<edgeindices.length/2; i++) {
 +            var sprite = new THREE.Sprite(spriteMaterial);
 +            sprite.position.copy(new THREE.Vector3().addVectors(points[edgeindices[2*i]].vector,points[edgeindices[2*i+1]].vector).multiplyScalar(0.5));
 +            edgelabels.add(sprite);
 +        }
 +    }
 +    obj.add(edgelabels);
 +}
 +
 +function init_arrowheads(obj) {
 +    var arrowheads = new THREE.Group();
 +    arrowheads.name = "arrowheads";
 +    var arrowstyle = obj.userData.arrowstyle;
 +    var edgeindices = obj.userData.edgeindices;
 +    var edgematerials = obj.userData.edgematerial;
 +    var points = obj.userData.points;
 +    var material;
 +    if (!Array.isArray(edgematerials)) {
 +        material = new THREE.MeshBasicMaterial( {color: edgematerials.color} );
 +    }
 +
 +    for (var i=0; i<edgeindices.length; i=i+2) {
 +        var start = points[edgeindices[i]];
 +        var end = points[edgeindices[i+1]];
 +        var dist = start.vector.distanceTo( end.vector ) - start.radius() - end.radius();
 +        if (dist <= 0) {
 +            continue;
 +        }
 +        var dir = new THREE.Vector3().subVectors(end.vector,start.vector);
 +        dir.normalize();
 +        var axis = new THREE.Vector3().set(dir.z,0,-dir.x);
 +        axis.normalize();
 +        var radians = Math.acos( dir.y );
 +        var radius = dist/25;
 +        var height = dist/5;
 +        var geometry = new THREE.ConeBufferGeometry(radius,height);
 +        var position = new THREE.Vector3().addVectors(start.vector,dir.clone().multiplyScalar(start.radius()+dist-height/2));
 +        if (Array.isArray(edgematerials)) {
 +            material = new THREE.MeshBasicMaterial( {color: edgematerials[i].color} );
 +        }
 +        var cone = new THREE.Mesh( geometry, material );
 +        cone.quaternion.setFromAxisAngle(axis,radians);;
 +        cone.position.copy(position);;
 +        arrowheads.add(cone);
 +    }
 +    obj.add(arrowheads);
 +}
 +
 +function init_faces(obj) {
 +    var points = obj.userData.points;
 +    var facets = obj.userData.facets;
 +    obj.userData.triangleindices = [];
 +    for (var i=0; i<facets.length; i++) {
 +        facet = facets[i];
 +        for (var t=0; t<facet.length-2; t++) {
 +            obj.userData.triangleindices.push(facet[0],facet[t+1],facet[t+2]);  
 +        }
 +    }
 +    var bufarr = new Float32Array( obj.userData.triangleindices.length * 3 );
 +    var bufattr = new THREE.Float32BufferAttribute(bufarr,3);
 +    
 +    var materials = obj.userData.facetmaterial;
 +    var geometry = new THREE.BufferGeometry();
 +    var frontmaterials = [];
 +    var backmaterials = [];
 +    geometry.setAttribute('position',bufattr);
 +    if (Array.isArray(materials)) {
 +        var tricount = 0;
 +        var facet;
 +        for (var i=0; i<facets.length; i++) {
 +            facet = facets[i];
 +            geometry.addGroup(tricount,(facet.length-2)*3,i);
 +            tricount += (facet.length-2)*3;
 +        }
 +        for (var j=0; j<materials.length; j++) {
 +            var fmat = materials[j].clone()
 +            fmat.side = THREE.FrontSide;
 +            frontmaterials.push(fmat);
 +            var bmat = materials[j].clone()
 +            bmat.side = THREE.BackSide;
 +            backmaterials.push(bmat);
 +            obj.userData.facetmaterial = frontmaterials.concat(backmaterials);
 +        }
 +    } else if (materials instanceof THREE.Material) {
 +        frontmaterials = materials.clone()
 +        frontmaterials.side = THREE.FrontSide;
 +        backmaterials = materials.clone()
 +        backmaterials.side = THREE.BackSide;
 +        obj.userData.facetmaterial = [frontmaterials, backmaterials];
 +    }
 +    // duplicating the object with front and back should avoid transparency issues
 +    var backmesh = new THREE.Mesh(geometry, backmaterials);
 +    // meshname is used to show/hide objects
 +    backmesh.name = "backfaces";
 +    obj.add(backmesh);
 +    var frontmesh = new THREE.Mesh(geometry, frontmaterials);
 +    frontmesh.name = "frontfaces";
 +    obj.add(frontmesh);
 +    updateFacesPosition(obj);
 +}
 +// //INITIALIZING
 +
 +
 +function updateFacesPosition(obj) {
 +    var points = obj.userData.points;
 +    var indices = obj.userData.triangleindices;
 +    var faces = obj.getObjectByName("frontfaces");
 +    var ba = faces.geometry.getAttribute("position");
 +    for (var i=0; i<indices.length; i++) {
 +        ba.setXYZ(i, points[indices[i]].vector.x, points[indices[i]].vector.y ,points[indices[i]].vector.z); 
 +    }
 +    faces.geometry.attributes.position.needsUpdate = true;
 +    
 +}
 +
 +function updateEdgesPosition(obj) {
 +    var points = obj.userData.points;
 +    var indices = obj.userData.edgeindices;
 +    var lines = obj.getObjectByName("lines");
 +    var ba = lines.geometry.getAttribute("position"); 
 +    for (var i=0; i<indices.length; i++) {
 +        ba.setXYZ(i, points[indices[i]].vector.x, points[indices[i]].vector.y ,points[indices[i]].vector.z); 
 +    }
 +    lines.geometry.attributes.position.needsUpdate = true;
 +}
 +
 +function onWindowResize() {
 +    renderer.setSize( three.clientWidth, three.clientHeight );
 +    svgRenderer.setSize( three.clientWidth, three.clientHeight );
 +    updateCamera();
 +}
 +
 +function updateCamera() {
 +    var width = three.clientWidth;
 +    var height = three.clientHeight;
 +    var aspect = width / height;
 +    if (camera.type == "OrthographicCamera") {
 +        camera.left = frustumSize * aspect / - 2;
 +        camera.right = frustumSize * aspect / 2;
 +        camera.top = frustumSize / 2;
 +        camera.bottom = - frustumSize / 2;
 +    } else if (camera.type == "PerspectiveCamera") {
 +        camera.aspect = aspect;
 +    }
 +    camera.updateProjectionMatrix();
 +}
 +
 +function changeCamera(event) {
 +    var selindex = event.currentTarget.selectedIndex;
 +    camera = cameras[selindex];
 +    control = controls[selindex];
 +    control.enabled = true; 
 +    for (var i=0; i<controls.length; i++) {
 +        if (i!=selindex) {
 +            controls[i].enabled = false;
 +        }
 +    }
 +    updateCamera();
 +}
 +
 +var camtypenode = document.getElementById('cameraType_0');
 +camtypenode.onchange = changeCamera;
 +camtypenode.dispatchEvent(new Event('change'));
 +
 +onWindowResize();
 +window.addEventListener('resize', onWindowResize);
 +
 +
 +var xRotationEnabled = false;
 +var yRotationEnabled = false;
 +var zRotationEnabled = false;
 +var rotationSpeedFactor = 1;
 +var settingsShown = false;
 +var labelsShown = true;
 +var intervals = [];
 +var timeouts = [];
 +var explodingSpeed = 0.05;
 +var explodeScale = 0.000001;
 +var XMLS = new XMLSerializer();
 +var svgElement;
 +var renderId;
 +
 +var render = function () {
 +
 + renderId = requestAnimationFrame(render);
 +
 +// comment in for automatic explosion
 +// explode(updateFactor());
 +
 +    var phi = 0.02 * rotationSpeedFactor;
 +
 +    if (xRotationEnabled) {
 +        scene.rotation.x += phi;
 +    }
 +    if (yRotationEnabled) {
 +        scene.rotation.y += phi;
 +    }
 +    if (zRotationEnabled) {
 +        scene.rotation.z += phi;
 +    }
 +
 +    control.update();
 +    renderer.render(scene, camera);
 +};
 +
 +if ( THREE.WEBGL.isWebGLAvailable() ) {
 + render();
 +} else {
 + var warning = WEBGL.getWebGLErrorMessage();
 + three.appendChild( warning );
 +}
 +    
 +function changeTransparency() {
 +    var opacity = 1-Number(event.currentTarget.value);
 +    for (var i=0; i<scene.children.length; i++) {
 +        child = scene.children[i];
 +        if ( child.userData.hasOwnProperty("facetmaterial") ) {
 +            if (Array.isArray(child.userData.facetmaterial)) {
 +                for (var j=0; j<child.userData.facetmaterial.length; j++) {
 +                    child.userData.facetmaterial[j].opacity = opacity;
 +                }
 +            } else {
 +                child.userData.facetmaterial.opacity = opacity;
 +            }    
 +        }
 +    }
 +}
 +
 +function toggleDepthWrite(event) {
 +    depthwrite = event.currentTarget.checked;
 +    for (var i=0; i<scene.children.length; i++) {
 +        child = scene.children[i];
 +        if ( child.userData.hasOwnProperty("facetmaterial") ) {
 +            if (Array.isArray(child.userData.facetmaterial)) {
 +                for (var j=0; j<child.userData.facetmaterial.length; j++) {
 +                    child.userData.facetmaterial[j].depthWrite = depthwrite;
 +                }
 +            } else {
 +                child.userData.facetmaterial.depthWrite = depthWrite;
 +            }    
 +        }
 +    }
 +}
 +
 +function changeRotationX(event){
 +    xRotationEnabled = event.currentTarget.checked;
 +}
 +
 +function changeRotationY(event){
 +    yRotationEnabled = event.currentTarget.checked;
 +}
 +
 +function changeRotationZ(event){
 +    zRotationEnabled = event.currentTarget.checked;
 +}
 +
 +
 +function changeRotationSpeedFactor(event){
 +    rotationSpeedFactor = Number(event.currentTarget.value);
 +}
 +
 +function resetScene(){
 +    scene.rotation.set(0,0,0);
 +    camera.position.set(0,0,5);
 +    camera.up.set(0,1,0);
 +}
 +
 +function showSettings(event){
 +    document.getElementById('settings_0').style.visibility = 'visible';
 +    document.getElementById('showSettingsButton_0').style.visibility = 'hidden';
 +    document.getElementById('hideSettingsButton_0').style.visibility = 'visible';
 +    settingsShown = true;
 +}
 +
 +function hideSettings(event){
 +    document.getElementById('settings_0').style.visibility = 'hidden';
 +    document.getElementById('showSettingsButton_0').style.visibility = 'visible';
 +    document.getElementById('hideSettingsButton_0').style.visibility = 'hidden';
 +    settingsShown = false;
 +}
 +
 +
 +
 +var pos = 150* Math.PI;
 +
 +function updateFactor() {
 +    pos++;
 +    return Math.sin(.01*pos)+1;
 +}
 +
 +// ------------------------ FOLDING ------------------------------------------------
 +// ---------------------------------------------------------------------------------
 +// rotate point p around axis defined by points p1 and p2 by given angle
 +function rotate(p, p1, p2, angle ){   
 +    angle = -angle;
 +    var x = p.x, y = p.y, z = p.z, 
 +    a = p1.x, b = p1.y, c = p1.z, 
 +    u = p2.x-p1.x, v = p2.y-p1.y, w = p2.z-p1.z;
 +    var result = [];
 +    var L = u*u + v*v + w*w;
 +    var sqrt = Math.sqrt;
 +    var cos = Math.cos;
 +    var sin = Math.sin;
 +
 +    result[0] = ((a*(v*v+w*w)-u*(b*v+c*w-u*x-v*y-w*z))*(1-cos(angle))+L*x*cos(angle)+sqrt(L)*(-c*v+b*w-w*y+v*z)*sin(angle))/L;
 +    result[1] = ((b*(u*u+w*w)-v*(a*u+c*w-u*x-v*y-w*z))*(1-cos(angle))+L*y*cos(angle)+sqrt(L)*(c*u-a*w+w*x-u*z)*sin(angle))/L;
 +    result[2] = ((c*(u*u+v*v)-w*(a*u+b*v-u*x-v*y-w*z))*(1-cos(angle))+L*z*cos(angle)+sqrt(L)*(-b*u+a*v-v*x+u*y)*sin(angle))/L;
 +
 +    return result;
 +}
 +
 +var fold = function(event){
 +    var obj = foldables[Number(event.currentTarget.name)];
 +    var foldvalue = Number(event.currentTarget.value);
 +    var scale = foldvalue - obj.userData.oldscale;
 +
 +    for (var j=0; j<obj.userData.axes.length; j++) {
 +        rotateVertices(obj, j, scale);
 +    }
 +    update(obj);
 +    obj.userData.oldscale += scale;
 +    lookAtBarycenter(obj);
 +}
 +
 +function lookAtBarycenter(obj){
 +    control.target = barycenter(obj);
 +}
 +
 +function barycenter(obj) {
 +    var center = new THREE.Vector3(0,0,0);
 +    var points = obj.userData.points;
 +    for (var i=0; i<points.length; i++){
 +        center.add(points[i].vector);
 +    }
 +    center.divideScalar(points.length);
 +    return center;
 +}
 +
 +function rotateVertices(obj, edge, scale) {
 +    var axes = obj.userData.axes;
 +    var subtrees = obj.userData.subtrees;
 +    var points = obj.userData.points;
 +    var angles = obj.userData.angles;
 +    if (edge < axes.length){
 +        for (var j=0; j<subtrees[edge].length; j++){
 +            var rotP = rotate(points[subtrees[edge][j]].vector, points[axes[edge][0]].vector,points[axes[edge][1]].vector, scale * (Math.PI - angles[edge]));
 +            points[subtrees[edge][j]].set(rotP[0],rotP[1],rotP[2]);
 +        }
 +    }
 +}
 +
 +function update(obj) {
 +   updateFacesPosition(obj);
 +   updateEdgesPosition(obj);
 +}
 +
 +if (foldables.length) {
 +    var settings = document.getElementById('settings_0');
 +    var foldDiv = document.createElement('div');
 +    foldDiv.id = 'fold_0';
 +    var title = document.createElement('strong');
 +    title.innerHTML = 'Fold';
 +    foldDiv.appendChild(title);
 +    foldDiv.className = 'group';
 +    for (var i=0; i<foldables.length; i++) {
 +        var range = document.createElement('input');
 +        range.type = 'range';
 +        range.min = 0;
 +        range.max = 1;
 +        range.value = 0;
 +        range.step = 0.001;
 +        range.name = String(i);
 +        range.oninput = fold;
 +        foldDiv.appendChild(range);
 +    }
 +    lookAtBarycenter(foldables[0]);
 +    settings.insertBefore(foldDiv,settings.childNodes[0]);
 +}
 +
 +    
 +// ---------------------- EXPLOSION ------------------------------------------------
 +// ---------------------------------------------------------------------------------
 +
 +if (explodableModel) {
 +    for (var i=0; i<scene.children.length; i++) {
 +        obj = scene.children[i];
 +        if ( obj.userData.explodable ) {
 +            computeCentroid(obj);
 +        }
 +    }
 +    document.getElementById('explodeRange_0').oninput = triggerExplode;
 +    document.getElementById('explodeCheckbox_0').onchange = triggerAutomaticExplode;
 +    document.getElementById('explodingSpeedRange_0').oninput = setExplodingSpeed;
 +    explode(0.000001);
 +}
 +
 +function computeCentroid(obj) {
 +    centroid = new THREE.Vector3();
 +    obj.userData.points.forEach(function(pmpoint) {
 +        centroid.add(pmpoint.vector);
 +    });
 +    centroid.divideScalar(obj.userData.points.length);
 +    obj.userData.centroid = centroid;
 +}
 +
 +function explode(factor) {
 +    for (var i=0; i<scene.children.length; i++) {
 +        var obj = scene.children[i];
 +        if (obj.userData.hasOwnProperty("centroid")) { 
 +            var c = obj.userData.centroid;
 +            obj.position.set(c.x*factor, c.y*factor, c.z*factor);
 +        }
 +    }
 +}
 +
 +function triggerExplode(event){
 +    explodeScale = Number(event.currentTarget.value);
 +    explode(explodeScale);
 +}
 +
 +function setExplodingSpeed(event){
 +    explodingSpeed = Number(event.currentTarget.value);
 +}
 +
 +function triggerAutomaticExplode(event){
 +    if (event.currentTarget.checked){
 +        startExploding();
 +    } else {
 +        clearIntervals();
 +    }
 +}
 +
 +function startExploding(){
 +    intervals.push(setInterval(explodingInterval, 25));
 +}
 +
 +
 +function explodingInterval(){
 +    explodeScale += explodingSpeed;
 +    if (explodeScale <= 6){ 
 +        explode(explodeScale);
 +    }
 +    else{
 +        explode(6);
 +        explodeScale = 6;
 +        clearIntervals();
 +        timeouts.push(setTimeout(startUnexploding, 3000));
 +    }
 +    document.getElementById('explodeRange_0').value = explodeScale;
 +}
 +
 +
 +function startUnexploding(){
 +    intervals.push(setInterval(unexplodingInterval, 25));
 +}
 +
 +function unexplodingInterval(){
 +    explodeScale -= explodingSpeed;
 +    if (explodeScale >= 0){
 +        explode(explodeScale);
 +    }
 +    else {
 +        explode(0);
 +        explodeScale = 0;
 +        clearIntervals();
 +        timeouts.push(setTimeout(startExploding, 3000));
 +    }
 +    document.getElementById('explodeRange_0').value = explodeScale;
 +}
 +
 +function clearIntervals(){
 +    intervals.forEach(function(interval){
 +        clearInterval(interval);
 +    });
 +    intervals = [];
 +    timeouts.forEach(function(timeout){
 +        clearTimeout(timeout);
 +    });
 +    timeouts = [];
 +}
 +
 +// ---------------------- DISPLAY --------------------------------------------------
 +// ---------------------------------------------------------------------------------
 +
 +const objectTypeInnerHTMLs = { points: "Points", pointlabels: "Point labels", lines: "Edges", edgelabels: "Edge labels", faces: "Faces", arrowheads: "Arrow heads" };
 +const objectTypeVisible = {};
 +Object.assign(objectTypeVisible,modelContains);
 +const sortedObjectTypeKeys = Object.keys(objectTypeInnerHTMLs).sort();
 +const shownObjectTypesList = document.getElementById('shownObjectTypesList_0');
 +
 +function setVisibility(bool,objname) {
 +    for (var i=0; i<scene.children.length; i++){
 +        var obj = scene.children[i].getObjectByName(objname);
 +        if (obj) {
 +            obj.visible = bool;
 +        }
 +    }
 +}
 +
 +function toggleObjectTypeVisibility(event){
 +    var name = event.currentTarget.name;
 +    var checked = event.currentTarget.checked;
 +    objectTypeVisible[name] = checked;
 +    if (name == "faces") {
 +        setVisibility(checked,"frontfaces");
 +        setVisibility(checked,"backfaces");
 +    } else {
 +        setVisibility(checked,name);
 +    }
 +}
 +
 +for (var i=0; i<sortedObjectTypeKeys.length; i++){
 +    var key = sortedObjectTypeKeys[i];
 +    if (modelContains[key]) {
 +        var objTypeNode = document.createElement('span');
 +        objTypeNode.innerHTML = objectTypeInnerHTMLs[key] + '<br>';
 +        var checkbox = document.createElement('input');
 +        checkbox.type = 'checkbox';
 +        checkbox.checked = true;
 +        checkbox.name = key;
 +        checkbox.onchange = toggleObjectTypeVisibility;
 +        shownObjectTypesList.appendChild(checkbox);
 +        shownObjectTypesList.appendChild(objTypeNode);
 +    }
 +}
 +
 +// ------------------------------------------------------
 +
 +function toggleObjectVisibility(event){
 +    var nr = Number(event.currentTarget.name);
 +    scene.children[nr].visible = event.currentTarget.checked;
 +}
 +
 +// append checkboxes for displaying or hiding objects
 +var shownObjectsList = document.getElementById('shownObjectsList_0');
 +for (var i=0; i<scene.children.length; i++){
 +    obj = scene.children[i];
 +    var objNode = document.createElement('span');
 +    objNode.innerHTML = obj.name + '<br>';
 +    var checkbox = document.createElement('input');
 +    checkbox.type = 'checkbox';
 +    checkbox.checked = true;
 +    checkbox.name = String(i);
 +    checkbox.onchange = toggleObjectVisibility;
 +    shownObjectsList.appendChild(checkbox);
 +    shownObjectsList.appendChild(objNode);
 +}
 +
 +// ---------------------- SVG ------------------------------------------------------
 +// ---------------------------------------------------------------------------------
 +
 +function takeSvgScreenshot() {
 +    if (objectTypeVisible["pointlabels"]) {
 +        setVisibility(false,"pointlabels");
 +    }
 +    if (objectTypeVisible["edgelabels"]) {
 +        setVisibility(false,"edgelabels");
 +    }
 +    svgRenderer.render(scene,camera);
 +    svgElement = XMLS.serializeToString(svgRenderer.domElement);
 +    
 +    if (objectTypeVisible["pointlabels"]) {
 +        setVisibility(true,"pointlabels");
 +    }
 +    if (objectTypeVisible["edgelabels"]) {
 +        setVisibility(true,"edgelabels");
 +    }
 +
 +    if (document.getElementById('tab_0').checked){
 +        //show in new tab
 +        var myWindow = window.open("","");
 +        myWindow.document.body.innerHTML = svgElement;
 +    } else{
 +        // download svg file 
 +        download("screenshot.svg", svgElement);
 +    }
 +}
 +
 +function download(filename, text) {
 +    var element = document.createElement('a');
 +    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
 +    element.setAttribute('download', filename);
 +
 +    element.style.display = 'none';
 +    document.body.appendChild(element);
 +
 +    element.click();
 +
 +    document.body.removeChild(element);
 +}
 +
 +
 +document.getElementById('transparencyRange_0').oninput = changeTransparency;
 +document.getElementById('depthWriteCheckbox_0').onchange = toggleDepthWrite;
 +document.getElementById('changeRotationX_0').onchange = changeRotationX;
 +document.getElementById('changeRotationY_0').onchange = changeRotationY;
 +document.getElementById('changeRotationZ_0').onchange = changeRotationZ;
 +document.getElementById('resetButton_0').onclick = resetScene;
 +document.getElementById('rotationSpeedRange_0').oninput = changeRotationSpeedFactor;
 +document.getElementById('takeScreenshot_0').onclick = takeSvgScreenshot;
 +document.getElementById('showSettingsButton_0').onclick = showSettings;
 +document.getElementById('hideSettingsButton_0').onclick = hideSettings;
 +
 +
 +// ------------------ SHORTCUTS --------------------------------------------
 +// -------------------------------------------------------------------------
 +
 +/**
 + * http://www.openjs.com/scripts/events/keyboard_shortcuts/
 + * Version : 2.01.B
 + * By Binny V A
 + * License : BSD
 + */
 +shortcut = {
 + 'all_shortcuts':{},//All the shortcuts are stored in this array
 + 'add': function(shortcut_combination,callback,opt) {
 + //Provide a set of default options
 + var default_options = {
 + 'type':'keydown',
 + 'propagate':false,
 + 'disable_in_input':false,
 + 'target':document,
 + 'keycode':false
 + }
 + if(!opt) opt = default_options;
 + else {
 + for(var dfo in default_options) {
 + if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo];
 + }
 + }
 +
 + var ele = opt.target;
 + if(typeof opt.target == 'string') ele = document.getElementById(opt.target);
 + var ths = this;
 + shortcut_combination = shortcut_combination.toLowerCase();
 +
 + //The function to be called at keypress
 + var func = function(e) {
 + e = e || window.event;
 +
 + if(opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields
 + var element;
 + if(e.target) element=e.target;
 + else if(e.srcElement) element=e.srcElement;
 + if(element.nodeType==3) element=element.parentNode;
 +
 + if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return;
 + }
 +
 + //Find Which key is pressed
 + if (e.keyCode) code = e.keyCode;
 + else if (e.which) code = e.which;
 + var character = String.fromCharCode(code).toLowerCase();
 +
 + if(code == 188) character=","; //If the user presses , when the type is onkeydown
 + if(code == 190) character="."; //If the user presses , when the type is onkeydown
 +
 + var keys = shortcut_combination.split("+");
 + //Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
 + var kp = 0;
 +
 + //Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
 + var shift_nums = {
 + "`":"~",
 + "1":"!",
 + "2":"@",
 + "3":"#",
 + "4":"$",
 + "5":"%",
 + "6":"^",
 + "7":"&",
 + "8":"*",
 + "9":"(",
 + "0":")",
 + "-":"_",
 + "=":"+",
 + ";":":",
 + "'":"\"",
 + ",":"<",
 + ".":">",
 + "/":"?",
 + "\\":"|"
 + }
 + //Special Keys - and their codes
 + var special_keys = {
 + 'esc':27,
 + 'escape':27,
 + 'tab':9,
 + 'space':32,
 + 'return':13,
 + 'enter':13,
 + 'backspace':8,
 +
 + 'scrolllock':145,
 + 'scroll_lock':145,
 + 'scroll':145,
 + 'capslock':20,
 + 'caps_lock':20,
 + 'caps':20,
 + 'numlock':144,
 + 'num_lock':144,
 + 'num':144,
 +
 + 'pause':19,
 + 'break':19,
 +
 + 'insert':45,
 + 'home':36,
 + 'delete':46,
 + 'end':35,
 +
 + 'pageup':33,
 + 'page_up':33,
 + 'pu':33,
 +
 + 'pagedown':34,
 + 'page_down':34,
 + 'pd':34,
 +
 + 'left':37,
 + 'up':38,
 + 'right':39,
 + 'down':40,
 +
 + 'f1':112,
 + 'f2':113,
 + 'f3':114,
 + 'f4':115,
 + 'f5':116,
 + 'f6':117,
 + 'f7':118,
 + 'f8':119,
 + 'f9':120,
 + 'f10':121,
 + 'f11':122,
 + 'f12':123
 + }
 +
 + var modifiers = { 
 + shift: { wanted:false, pressed:false},
 + ctrl : { wanted:false, pressed:false},
 + alt  : { wanted:false, pressed:false},
 + meta : { wanted:false, pressed:false} //Meta is Mac specific
 + };
 +                        
 + if(e.ctrlKey) modifiers.ctrl.pressed = true;
 + if(e.shiftKey) modifiers.shift.pressed = true;
 + if(e.altKey) modifiers.alt.pressed = true;
 + if(e.metaKey)   modifiers.meta.pressed = true;
 +                        
 + for(var i=0; k=keys[i],i<keys.length; i++) {
 + //Modifiers
 + if(k == 'ctrl' || k == 'control') {
 + kp++;
 + modifiers.ctrl.wanted = true;
 +
 + } else if(k == 'shift') {
 + kp++;
 + modifiers.shift.wanted = true;
 +
 + } else if(k == 'alt') {
 + kp++;
 + modifiers.alt.wanted = true;
 + } else if(k == 'meta') {
 + kp++;
 + modifiers.meta.wanted = true;
 + } else if(k.length > 1) { //If it is a special key
 + if(special_keys[k] == code) kp++;
 +
 + } else if(opt['keycode']) {
 + if(opt['keycode'] == code) kp++;
 +
 + } else { //The special keys did not match
 + if(character == k) kp++;
 + else {
 + if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase
 + character = shift_nums[character]; 
 + if(character == k) kp++;
 + }
 + }
 + }
 + }
 +
 + if(kp == keys.length && 
 + modifiers.ctrl.pressed == modifiers.ctrl.wanted &&
 + modifiers.shift.pressed == modifiers.shift.wanted &&
 + modifiers.alt.pressed == modifiers.alt.wanted &&
 + modifiers.meta.pressed == modifiers.meta.wanted) {
 + callback(e);
 +
 + if(!opt['propagate']) { //Stop the event
 + //e.cancelBubble is supported by IE - this will kill the bubbling process.
 + e.cancelBubble = true;
 + e.returnValue = false;
 +
 + //e.stopPropagation works in Firefox.
 + if (e.stopPropagation) {
 + e.stopPropagation();
 + e.preventDefault();
 + }
 + return false;
 + }
 + }
 + }
 + this.all_shortcuts[shortcut_combination] = {
 + 'callback':func, 
 + 'target':ele, 
 + 'event': opt['type']
 + };
 + //Attach the function with the event
 + if(ele.addEventListener) ele.addEventListener(opt['type'], func, false);
 + else if(ele.attachEvent) ele.attachEvent('on'+opt['type'], func);
 + else ele['on'+opt['type']] = func;
 + },
 +
 + //Remove the shortcut - just specify the shortcut and I will remove the binding
 + 'remove':function(shortcut_combination) {
 + shortcut_combination = shortcut_combination.toLowerCase();
 + var binding = this.all_shortcuts[shortcut_combination];
 + delete(this.all_shortcuts[shortcut_combination])
 + if(!binding) return;
 + var type = binding['event'];
 + var ele = binding['target'];
 + var callback = binding['callback'];
 +
 + if(ele.detachEvent) ele.detachEvent('on'+type, callback);
 + else if(ele.removeEventListener) ele.removeEventListener(type, callback, false);
 + else ele['on'+type] = false;
 + }
 +}
 +
 +shortcut.add("Alt+Left",function() {
 + var event = new Event('click');
 + if (settingsShown){
 + document.getElementById('hideSettingsButton_0').dispatchEvent(event);
 + } else {
 + document.getElementById('showSettingsButton_0').dispatchEvent(event);
 + }
 +});
 +
 +
 +// COMMON_CODE_BLOCK_END
 +
 +});});
 +      </script>
 +   </body>
 +</html>
 +</HTML>
 +The command ''%%LATTICE_COLORED%%'' sorted the lattice points into three classes before visualization: lattice points in the interior of the polytope, lattice points on the boundary, and vertices that are not in the lattice. These classes are then visualized with different colors (where we only see two in the above picture, as all vertices of the cube are in the lattice). If you don't need this distinction, ''%%VISUAL->LATTICE%%'' avoids the additional computations.
 +
 +===== External Packages =====
 +
 +''%%polymake%%'' can use ''%%4ti2%%'' and ''%%lattE%%'' via a file based interface and ''%%libnormaliz >= 3.1.0%%'' as library, (the file based interface to ''%%normaliz%%'' has been discontinued) for lattice computations and prints all available packages during startup. To tell ''%%polymake%%'' about a newly installed program run ''%%polymake --reconfigure%%'' or issue the command ''%%reconfigure%%'' during the interactive session. polymake may ask you to confirm the paths to the binaries.
 +
 +<code>
 +Application polytope uses following third-party software (for details: help 'credits';)
 +4ti2, cddlib, latte, libnormaliz, lrslib, nauty
 +</code>
 +The output at this position depends on the software available on your computer. To see each call to an external program you can set the variable ''%%$Verbose::external=1;%%''. If you just want to see the credit message instead of the program call, set ''%%$Verbose::credits=2%%'' instead. If this is 1, then a credit is shown when a package is used for the first time, if 0, then all credits are suppressed (but you can find them in data files afterwards).
 +
 +<code perl>
 +> $Verbose::external=1;
 +> print $p->EHRHART_POLYNOMIAL;
 +8*x^3 + 12*x^2 + 6*x + 1
 +</code>
 +You can ask ''%%polymake%%'' to prefer one package over another by setting ''%%prefer "program";%%'' where program is one of ''%%_4ti2%%'', ''%%latte%%'' and ''%%normaliz2%%''. Of course, the corresponding package needs to be installed on your computer.
 +
 +To prefer one program only for some computations you may append one of .integer_points, .hilbert, .ehrhartpoly for rules computing N_LATTICE_POINTS, LATTICE_POINTS, HILBERT_BASIS or EHRHART_POLYNOMIAL. (Or ''%%prefer_now%%'' just for the next computation)
 +
 +<code perl>
 +> print cube(2)->N_LATTICE_POINTS;
 +9
 +> prefer_now "libnormaliz";
 +> print cube(2)->N_LATTICE_POINTS;
 +9
 +> print cube(2)->EHRHART_POLYNOMIAL;
 +4*x^2 + 4*x + 1
 +</code>