# A Longer Introduction to Polyhedral Computations in Sage¶

*Author: Jean-Philippe Labbé <labbe@math.fu-berlin.de>*

This tutorial aims to showcase some of the possibilities of Sage concerning polyhedral geometry and combinatorics.

The classic literature on the topic includes:

For the nomenclature, Sage tries to follow:

Handbook of Discrete and Computational Geometry, Chapter 15, [Goo2004]

Preparation of this document was supported in part by the OpenDreamKit project and written during the SageDays 84 in Olot (Spain).

## Lecture 0: Basic definitions and constructions¶

A real \((k\times d)\)-matrix \(A\) and a real vector \(b\)
in \(\mathbb{R}^d\) define a (convex) **polyhedron** \(P\) as the set of solutions
of the system of linear inequalities:

Each row of \(A\) defines a closed half-space of \(\mathbb{R}^d\).
Hence a polyhedron is the intersection of finitely many closed half-spaces in
\(\mathbb{R}^d\). The matrix \(A\) may contain equal rows, which may lead to a
set of *equalities* satisfied by the polyhedron. If there are no redundant rows
in the above definition, this definition is referred to as the
\(\mathbf{H}\) **-representation** of a polyhedron.

A maximal affine subspace \(L\) contained in a polyhedron is a **lineality** space of
\(P\). Fixing a point \(o\) of the lineality space \(L\) to act
as the *origin*, one can write every point \(p\) inside a polyhedron as a combination

where \(\ell\in L\) (using \(o\) as the origin), \(\sum_{i=1}^n\lambda_i=1\), \(\lambda_i\geq0\), \(\mu_i\geq0\), and \(r_i\neq0\) for all \(0\leq i\leq m\) and the set of \(r_i\) ‘s are positively independent (the origin is not in their positive span). For a given point \(p\) there may be many equivalent ways to write the above using different sets \(\{v_i\}_{i=1}^{n}\) and \(\{r_i\}_{i=1}^{m}\). Hence we require the sets to be inclusion minimal sets such that we can get the above property equality for any point \(p\in P\).

The vectors \(v_i\) are called the *vertices* of \(P\) and the vectors
\(r_i\) are called the *rays* of \(P\).
This way to represent a polyhedron is referred to as the
\(\mathbf{V}\) **-representation** of a polyhedron. The first sum represents the *convex
hull* of the vertices \(v_i\) ‘s and the second sum represents a *pointed
polyhedral cone* generated by finitely many rays.

When the lineality space and the rays are reduced to a point (i.e. no rays and
no lines) the object is often referred to as a **polytope**.

Note

As mentioned in the documentation of the constructor when typing `Polyhedron?`

:

*You may either define it with vertex/ray/line or
inequalities/equations data, but not both. Redundant data will
automatically be removed (unless “minimize=False”), and the
complementary representation will be computed.*

Here is the documentation for the constructor function of Polyhedra.

### \(V\)-representation¶

First, let’s define a polyhedron object as the convex hull of a set of points and some rays.

```
sage: P1 = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]])
sage: P1
A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices and 1 ray
```

The string representation already gives a lot of information:

- the dimension of the polyhedron (the smallest affine space containing it)
- the dimension of the space in which it is defined
- the base ring (\(\mathbb{Z}^2\)) over which the polyhedron lives (this specifies the parent class, see Parents for Polyhedra)
- the number of vertices
- the number of rays

Of course, you want to know what this object looks like:

```
sage: P1.plot()
Graphics object consisting of 5 graphics primitives
```

We can also add a lineality space.

```
sage: P2 = Polyhedron(vertices = [[1/2, 0, 0], [0, 1/2, 0]],
....: rays = [[1, 1, 0]],
....: lines = [[0, 0, 1]])
sage: P2
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 2 vertices, 1 ray, 1 line
sage: P2.plot()
Graphics3d Object
```

Notice that the base ring changes because of the value \(\frac{1}{2}\). Indeed, Sage finds an appropriate ring to define the object.

```
sage: P1.parent()
Polyhedra in ZZ^2
sage: P2.parent()
Polyhedra in QQ^3
```

The chosen ring depends on the input format.

```
sage: P3 = Polyhedron(vertices = [[0.5, 0], [0, 0.5]])
sage: P3
A 1-dimensional polyhedron in RDF^2 defined as the convex hull of 2 vertices
sage: P3.parent()
Polyhedra in RDF^2
```

Warning

The base ring `RDF`

should be used with care. As it is not an exact
ring, certain computations may break or silently produce wrong results, for
example when dealing with non-simplicial polyhedra.

The following example demonstrates the limitations of `RDF`

.

```
sage: D = polytopes.dodecahedron()
sage: D
A 3-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5)^3 defined as the convex hull of 20 vertices
sage: D_RDF = Polyhedron(vertices = [n(v.vector(),digits=6) for v in D.vertices()], base_ring=RDF)
Traceback (most recent call last):
...
ValueError: *Error: Numerical inconsistency is found. Use the GMP exact arithmetic.
```

If the input of the polyhedron consists of python `float`

, it
automatically converts the data to `RDF`

:

```
sage: Polyhedron(vertices=[[float(1.1)]])
A 0-dimensional polyhedron in RDF^1 defined as the convex hull of 1 vertex
```

It is also possible to define polyhedron over algebraic numbers.

```
sage: sqrt_2 = AA(2)^(1/2)
sage: cbrt_2 = AA(2)^(1/3)
sage: timeit('Polyhedron(vertices = [[sqrt_2, 0], [0, cbrt_2]])') # random
5 loops, best of 3: 43.2 ms per loop
sage: P4 = Polyhedron(vertices = [[sqrt_2, 0], [0, cbrt_2]]); P4
A 1-dimensional polyhedron in AA^2 defined as the convex hull of 2 vertices
```

There is another way to create a polyhedron over algebraic numbers:

```
sage: K.<a> = NumberField(x^2 - 2, embedding=AA(2)**(1/2))
sage: L.<b> = NumberField(x^3 - 2, embedding=AA(2)**(1/3))
sage: timeit('Polyhedron(vertices = [[a, 0], [0, b]])') # random
5 loops, best of 3: 39.9 ms per loop
sage: P5 = Polyhedron(vertices = [[a, 0], [0, b]]); P5
A 1-dimensional polyhedron in AA^2 defined as the convex hull of 2 vertices
```

If the base ring is known it may be a good option to use the proper `sage.rings.number_field.number_field.number_field.composite_fields()`

:

```
sage: J = K.composite_fields(L)[0]
sage: timeit('Polyhedron(vertices = [[J(a), 0], [0, J(b)]])') # random
25 loops, best of 3: 9.8 ms per loop
sage: P5_comp = Polyhedron(vertices = [[J(a), 0], [0, J(b)]]); P5_comp
A 1-dimensional polyhedron in (Number Field in ab with defining polynomial x^6 - 6*x^4 - 4*x^3 + 12*x^2 - 24*x - 4)^2 defined as the convex hull of 2 vertices
```

Polyhedral computations with the `Symbolic Ring`

is not implemented.
It is not possible to define a polyhedron over it:

```
sage: sqrt_2s = sqrt(2)
sage: cbrt_2s = 2^(1/3)
sage: Polyhedron(vertices = [[sqrt_2s, 0], [0, cbrt_2s]])
Traceback (most recent call last):
...
ValueError: the only allowed inexact ring is 'RDF' with backend 'cdd'
```

Similarly, it is not possible to create polyhedron objects over `RR`

(no matter how many bits of precision).

```
sage: F45 = RealField(45)
sage: F100 = RealField(100)
sage: f = 1.1
sage: Polyhedron(vertices=[[F45(f)]])
Traceback (most recent call last):
...
ValueError: the only allowed inexact ring is 'RDF' with backend 'cdd'
sage: Polyhedron(vertices=[[F100(f)]])
Traceback (most recent call last):
...
ValueError: the only allowed inexact ring is 'RDF' with backend 'cdd'
```

There is one exception, when the number of bits of precision is 53, then the
base ring is converted to `RDF`

:

```
sage: F53 = RealField(53)
sage: Polyhedron(vertices=[[F53(f)]])
A 0-dimensional polyhedron in RDF^1 defined as the convex hull of 1 vertex
sage: type(Polyhedron(vertices=[[F53(f)]]))
<class 'sage.geometry.polyhedron.parent.Polyhedra_RDF_cdd_with_category.element_class'>
```

This behavior can be seen as wrong, but it allows the following to be acceptable by Sage:

```
sage: Polyhedron([(1.0, 2.3), (3.5, 2.0)])
A 1-dimensional polyhedron in RDF^2 defined as the convex hull of 2 vertices
```

without having specified the base ring `RDF`

by the user.

### \(H\)-representation¶

If a polyhedron object was constructed via a \(V\)-representation, Sage can provide the \(H\)-representation of the object.

```
sage: for h in P1.Hrepresentation():
....: print(h)
An inequality (1, 1) x - 1 >= 0
An inequality (1, -1) x + 1 >= 0
An inequality (-1, 1) x + 1 >= 0
```

Each line gives a row of the matrix \(A\) and an entry of the vector \(b\).
The variable \(x\) is a vector in the ambient space where `P1`

is
defined. The \(H\)-representation may contain equations:

```
sage: P3.Hrepresentation()
(An inequality (-2.0, 0.0) x + 1.0 >= 0,
An inequality (1.0, 0.0) x + 0.0 >= 0,
An equation (1.0, 1.0) x - 0.5 == 0)
```

The construction of a polyhedron object via its \(H\)-representation,
requires a precise format. Each inequality \((a_{i1}, \dots, a_{id})\cdot
x + b_i \geq 0\) must be written as `[b_i,a_i1, ..., a_id]`

.

```
sage: P3_H = Polyhedron(ieqs = [[1.0, -2, 0], [0, 1, 0]], eqns = [[-0.5, 1, 1]])
sage: P3 == P3_H
True
sage: P3_H.Vrepresentation()
(A vertex at (0.0, 0.5), A vertex at (0.5, 0.0))
```

It is worth using the parameter `eqns`

to shorten the construction of the
object. In the following example, the first four rows are the negative of the
second group of four rows.

```
sage: H = [[0, 0, 0, 0, 0, 0, 0, 0, 1],
....: [0, 0, 0, 0, 0, 0, 1, 0, 0],
....: [-2, 1, 1, 1, 1, 1, 0, 0, 0],
....: [0, 0, 0, 0, 0, 0, 0, 1, 0],
....: [0, 0, 0, 0, 0, 0, 0, 0, -1],
....: [0, 0, 0, 0, 0, 0, -1, 0, 0],
....: [2, -1, -1, -1, -1, -1, 0, 0, 0],
....: [0, 0, 0, 0, 0, 0, 0, -1, 0],
....: [2, -1, -1, -1, -1, 0, 0, 0, 0],
....: [0, 0, 0, 0, 1, 0, 0, 0, 0],
....: [0, 0, 0, 1, 0, 0, 0, 0, 0],
....: [0, 0, 1, 0, 0, 0, 0, 0, 0],
....: [-1, 1, 1, 1, 1, 0, 0, 0, 0],
....: [1, 0, 0, -1, 0, 0, 0, 0, 0],
....: [0, 1, 0, 0, 0, 0, 0, 0, 0],
....: [1, 0, 0, 0, -1, 0, 0, 0, 0],
....: [1, 0, -1, 0, 0, 0, 0, 0, 0],
....: [1, -1, 0, 0, 0, 0, 0, 0, 0]]
sage: timeit('Polyhedron(ieqs = H)') # random
125 loops, best of 3: 5.99 ms per loop
sage: timeit('Polyhedron(ieqs = H[8:], eqns = H[:4])') # random
125 loops, best of 3: 4.78 ms per loop
sage: Polyhedron(ieqs = H) == Polyhedron(ieqs = H[8:], eqns = H[:4])
True
```

Of course, this is a toy example, but it is generally worth to preprocess the data before defining the polyhedron if possible.

## Lecture 1: Representation objects¶

Many objects are related to the \(H\)- and \(V\)-representations. Sage has classes implemented for them.

### \(H\)-representation¶

You can store the \(H\)-representation in a variable and use the inequalities and equalities as objects.

```
sage: P3_QQ = Polyhedron(vertices = [[0.5, 0], [0, 0.5]], base_ring=QQ)
sage: HRep = P3_QQ.Hrepresentation()
sage: H1 = HRep[0]; H1
An equation (2, 2) x - 1 == 0
sage: H2 = HRep[1]; H2
An inequality (0, -2) x + 1 >= 0
sage: H1.<tab> # not tested
sage: H1.A()
(2, 2)
sage: H1.b()
-1
sage: H1.is_equation()
True
sage: H1.is_inequality()
False
sage: H1.contains(vector([0,0]))
False
sage: H2.contains(vector([0,0]))
True
sage: H1.is_incident(H2)
True
```

It is possible to obtain the different objects of the \(H\)-representation as follows.

```
sage: P3_QQ.equations()
(An equation (2, 2) x - 1 == 0,)
sage: P3_QQ.inequalities()
(An inequality (0, -2) x + 1 >= 0, An inequality (0, 1) x + 0 >= 0)
```

Note

It is recommended to use `equations`

or `equation_generator`

(and similarly for inequalities) if one wants to iterate over them instead
of `equations_list`

.

### \(V\)-representation¶

Similarly, you can access vertices, rays and lines of the polyhedron.

```
sage: VRep = P2.Vrepresentation(); VRep
(A line in the direction (0, 0, 1),
A vertex at (0, 1/2, 0),
A vertex at (1/2, 0, 0),
A ray in the direction (1, 1, 0))
sage: L = VRep[0]; L
A line in the direction (0, 0, 1)
sage: V = VRep[1]; V
A vertex at (0, 1/2, 0)
sage: R = VRep[3]; R
A ray in the direction (1, 1, 0)
sage: L.is_line()
True
sage: L.is_incident(V)
True
sage: R.is_incident(L)
False
sage: L.vector()
(0, 0, 1)
sage: V.vector()
(0, 1/2, 0)
```

It is possible to obtain the different objects of the \(V\)-representation as follows.

```
sage: P2.vertices()
(A vertex at (0, 1/2, 0), A vertex at (1/2, 0, 0))
sage: P2.rays()
(A ray in the direction (1, 1, 0),)
sage: P2.lines()
(A line in the direction (0, 0, 1),)
sage: P2.vertices_matrix()
[ 0 1/2]
[1/2 0]
[ 0 0]
```

Note

It is recommended to use `vertices`

or `vertex_generator`

(and similarly for rays and lines) if one wants to iterate over them instead
of `vertices_list`

.

## Lecture 2: Backends for polyhedral computations¶

To deal with polyhedron objects, Sage currently has four backends available. These backends offer various functionalities and have their own specific strengths and limitations.

- The cdd backend for polyhedral computations
- The PPL (Parma Polyhedra Library) backend for polyhedral computations
- The polymake backend for polyhedral computations
- The Python backend

- This is a
`python`

backend that provides an implementation of polyhedron over irrational coordinates.- The Normaliz backend for polyhedral computations, (requires the optional package
`pynormaliz`

)

The default backend is `ppl`

. Whenever one needs **speed** it is good to try out
the different backends. The backend `field`

is **not** specifically designed
for dealing with extremal computations but can do computations in exact
coordinates.

### The `cdd`

backend¶

In order to use a specific backend, we specify the `backend`

parameter.

```
sage: P1_cdd = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='cdd')
sage: P1_cdd
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices and 1 ray
```

A priori, it seems that nothing changed, but …

```
sage: P1_cdd.parent()
Polyhedra in QQ^2
```

The polyhedron `P1_cdd`

is now considered as a rational polyhedron by the
backend `cdd`

. We can also check the backend and the parent using
`type`

:

```
sage: type(P1_cdd)
<class 'sage.geometry.polyhedron.parent.Polyhedra_QQ_cdd_with_category.element_class'>
sage: type(P1)
<class 'sage.geometry.polyhedron.parent.Polyhedra_ZZ_ppl_with_category.element_class'>
```

We see

- the backend used (ex:
`backend_cdd`

)- followed by a dot ‘.’
- the parent (ex:
`Polyhedra_QQ`

) followed again by the backend,

and you can safely ignore the rest for the purpose of this tutorial.

The `cdd`

backend accepts also entries in `RDF`

:

```
sage: P3_cdd = Polyhedron(vertices = [[0.5, 0], [0, 0.5]], backend='cdd')
sage: P3_cdd
A 1-dimensional polyhedron in RDF^2 defined as the convex hull of 2 vertices
```

but not algebraic or symbolic values:

```
sage: P4_cdd = Polyhedron(vertices = [[sqrt_2, 0], [0, cbrt_2]], backend='cdd')
Traceback (most recent call last):
...
ValueError: No such backend (=cdd) implemented for given basering (=Algebraic Real Field).
sage: P5_cdd = Polyhedron(vertices = [[sqrt_2s, 0], [0, cbrt_2s]], backend='cdd')
Traceback (most recent call last):
...
ValueError: the only allowed inexact ring is 'RDF' with backend 'cdd'
```

It is possible to get the `cdd`

format of any polyhedron object defined
over \(\mathbb{Z}\), \(\mathbb{Q}\), or `RDF`

:

```
sage: print(P1.cdd_Vrepresentation())
V-representation
begin
3 3 rational
0 1 1
1 0 1
1 1 0
end
sage: print(P3.cdd_Hrepresentation())
H-representation
linearity 1 1
begin
3 3 real
-0.5 1.0 1.0
1.0 -2.0 0.0
0.0 1.0 0.0
end
```

You can also write this data to a file using the method `.write_cdd_Hrepresentation(filename)`

or `.write_cdd_Vrepresentation(filename)`

, where `filename`

is a
string containing a path to a file to be written.

### The `ppl`

backend¶

The default backend for polyhedron objects is `ppl`

.

```
sage: type(P1)
<class 'sage.geometry.polyhedron.parent.Polyhedra_ZZ_ppl_with_category.element_class'>
sage: type(P2)
<class 'sage.geometry.polyhedron.parent.Polyhedra_QQ_ppl_with_category.element_class'>
sage: type(P3) # has entries like 0.5
<class 'sage.geometry.polyhedron.parent.Polyhedra_RDF_cdd_with_category.element_class'>
```

As you see, it does not accepts values in `RDF`

and the polyhedron constructor
used the `cdd`

backend.

### The `polymake`

backend¶

The `polymake`

backend is provided when the experimental package polymake
for sage is installed.

```
sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], # optional - polymake
....: rays=[(1,1)], lines=[],
....: backend='polymake', base_ring=QQ)
```

An example with quadratic field:

```
sage: V = polytopes.dodecahedron().vertices_list()
sage: Polyhedron(vertices=V, backend='polymake') # optional - polymake
A 3-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5)^3 defined as the convex hull of 20 vertices
```

### The `field`

backend¶

As it turns out, the rational numbers do not suffice to represent all combinatorial types of polytopes. For example, Perles constructed a \(8\)-dimensional polytope with \(12\) vertices which does not have a realization with rational coordinates, see Example 6.21 p. 172 of [Zie2007]. Furthermore, if one wants a realization to have specific geometric property, such as symmetry, one also sometimes need irrational coordinates.

The backend `field`

provides the necessary tools to deal with such
examples.

```
sage: type(D)
<class 'sage.geometry.polyhedron.parent.Polyhedra_field_with_category.element_class'>
```

Any time that the coordinates should be in an extension of the rational, the
backend `field`

is called.

```
sage: P4.parent()
Polyhedra in AA^2
sage: P5.parent()
Polyhedra in AA^2
sage: type(P4)
<class 'sage.geometry.polyhedron.parent.Polyhedra_field_with_category.element_class'>
sage: type(P5)
<class 'sage.geometry.polyhedron.parent.Polyhedra_field_with_category.element_class'>
```

### The `normaliz`

backend¶

The fourth backend is `normaliz`

and is an optional Sage package.

```
sage: P1_normaliz = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='normaliz') # optional - pynormaliz
sage: type(P1_normaliz) # optional - pynormaliz
<class 'sage.geometry.polyhedron.parent.Polyhedra_ZZ_normaliz_with_category.element_class'>
sage: P2_normaliz = Polyhedron(vertices = [[1/2, 0, 0], [0, 1/2, 0]], # optional - pynormaliz
....: rays = [[1, 1, 0]],
....: lines = [[0, 0, 1]], backend='normaliz')
sage: type(P2_normaliz) # optional - pynormaliz
<class 'sage.geometry.polyhedron.parent.Polyhedra_QQ_normaliz_with_category.element_class'>
```

This backend does not work with `RDF`

, or algebraic numbers or the `Symbolic Ring`

:

```
sage: P3_normaliz = Polyhedron(vertices = [[0.5, 0], [0, 0.5]], backend='normaliz') # optional - pynormaliz
Traceback (most recent call last):
...
ValueError: No such backend (=normaliz) implemented for given basering (=Real Double Field).
sage: P4_normaliz = Polyhedron(vertices = [[sqrt_2, 0], [0, cbrt_2]], backend='normaliz') # optional - pynormaliz
Traceback (most recent call last):
...
ValueError: No such backend (=normaliz) implemented for given basering (=Algebraic Real Field).
sage: P5_normaliz = Polyhedron(vertices = [[sqrt_2s, 0], [0, cbrt_2s]], backend='normaliz') # optional - pynormaliz
Traceback (most recent call last):
...
ValueError: the only allowed inexact ring is 'RDF' with backend 'cdd'
```

The backend `normaliz`

provides other methods such as
`integral_hull`

, which also works on unbounded polyhedron.

```
sage: P6 = Polyhedron(vertices = [[0, 0], [3/2, 0], [3/2, 3/2], [0, 3]], backend='normaliz') # optional - pynormaliz
sage: IH = P6.integral_hull(); IH # optional - pynormaliz
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
sage: P6.plot(color='blue')+IH.plot(color='red') # optional - pynormaliz
Graphics object consisting of 12 graphics primitives
sage: P1_normaliz.integral_hull() # optional - pynormaliz
A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices and 1 ray
```

## Lecture 3: To every polyhedron, the proper parent class¶

In order to **know all the methods that a polyhedron object has** one has to look into its `base class`

:

- Base class for polyhedra : This is the generic class for Polyhedron related objects.
- Base class for polyhedra over Z
- Base class for polyhedra over Q
- Base class for polyhedra over RDF.

Don’t be surprised if the classes look empty! The classes mainly contain private methods that implement some comparison methods: to verify equality and inequality of numbers in the base ring and other internal functionalities.

To get a full overview of methods offered to you, Base class for polyhedra is the first place you want to go.

## Lecture 4: A library of polytopes¶

There are a lot of polytopes that are readily available in the library, see Library of commonly used, famous, or interesting polytopes. Have a look at them to see if your polytope is already defined!

```
sage: A = polytopes.buckyball(); A # can take long
A 3-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5)^3 defined as the convex hull of 60 vertices
sage: B = polytopes.cross_polytope(4); B
A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 8 vertices
sage: C = polytopes.cyclic_polytope(3,10); C
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 10 vertices
sage: E = polytopes.snub_cube(); E
A 3-dimensional polyhedron in RDF^3 defined as the convex hull of 24 vertices
sage: polytopes.<tab> # not tested, to view all the possible polytopes
```

## Bibliography¶

[Bro1983] | Brondsted, A., An Introduction to Convex Polytopes, volume 90 of Graduate Texts in Mathematics. Springer-Verlag, New York, 1983. ISBN 978-1-4612-7023-2 |

[Goo2004] | J.E. Goodman and J. O’Rourke, editors, CRC Press LLC, Boca Raton, FL, 2004. ISBN 978-1584883012 (65 chapters, xvii + 1539 pages). |

[Gru1967] | Grünbaum, B., Convex polytopes, volume 221 of Graduate Texts in Mathematics. Springer-Verlag, New York, 2003. ISBN 978-1-4613-0019-9 |

[Zie2007] | (1, 2) Ziegler, G. M., Lectures on polytopes, volume 152 of Graduate
Texts in Mathematics. Springer-Verlag, New York, 2007.
ISBN 978-0-387-94365-7 |