Skip to content
This repository was archived by the owner on Feb 1, 2023. It is now read-only.

Commit 1fc0508

Browse files
author
Release Manager
committed
Trac #29065: Inverse function for gale_transform
One can obtain a gale diagram from a polyhedron. Some interesting examples of polyhedra are obtained by the reverse approach. We implement a method that converts a gale diagram to a `Polyhedron`. This is a preparation for adding polytopes to the library obtained from the Gale diagram (e.g. #27728). URL: https://trac.sagemath.org/29065 Reported by: gh-kliem Ticket author(s): Jonathan Kliem Reviewer(s): Jean-Philippe Labbé
2 parents aa09eef + b6898dd commit 1fc0508

File tree

2 files changed

+320
-0
lines changed

2 files changed

+320
-0
lines changed

src/sage/geometry/polyhedron/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3537,6 +3537,10 @@ def gale_transform(self):
35373537
35383538
Lectures in Geometric Combinatorics, R.R.Thomas, 2006, AMS Press.
35393539
3540+
.. SEEALSO::
3541+
3542+
:func`~sage.geometry.polyhedron.library.gale_transform_to_polyhedron`.
3543+
35403544
TESTS::
35413545
35423546
sage: P = Polyhedron(rays=[[1,0,0]])

src/sage/geometry/polyhedron/library.py

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,322 @@ def project_points(*points, **kwds):
193193
return [m * v for v in vecs]
194194

195195

196+
def gale_transform_to_polytope(vectors, base_ring=None, backend=None):
197+
r"""
198+
Return the polytope associated to the list of vectors forming a Gale transform.
199+
200+
This function is the inverse of
201+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.gale_transform`
202+
up to projective transformation.
203+
204+
INPUT:
205+
206+
- ``vectors`` -- the vectors of the Gale transform
207+
208+
- ``base_ring`` -- string (default: `None`);
209+
the base ring to be used for the construction
210+
211+
- ``backend`` -- string (default: `None`);
212+
the backend to use to create the polytope
213+
214+
.. NOTE::
215+
216+
The order of the input vectors will not be preserved.
217+
218+
If the center of the (input) vectors is the origin,
219+
the function is much faster and might give a nicer representation
220+
of the polytope.
221+
222+
If this is not the case, the vectors will be scaled
223+
(each by a positive scalar) accordingly to obtain the polytope.
224+
225+
.. SEEALSO::
226+
227+
:func`~sage.geometry.polyhedron.library.gale_transform_to_primal`.
228+
229+
EXAMPLES::
230+
231+
sage: from sage.geometry.polyhedron.library import gale_transform_to_polytope
232+
sage: points = polytopes.octahedron().gale_transform()
233+
sage: points
234+
((0, -1), (-1, 0), (1, 1), (1, 1), (-1, 0), (0, -1))
235+
sage: P = gale_transform_to_polytope(points); P
236+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 6 vertices
237+
sage: P.vertices()
238+
(A vertex at (-1, 0, 0),
239+
A vertex at (0, -1, 0),
240+
A vertex at (0, 0, -1),
241+
A vertex at (0, 0, 1),
242+
A vertex at (0, 1, 0),
243+
A vertex at (1, 0, 0))
244+
245+
One can specify the base ring::
246+
247+
sage: gale_transform_to_polytope(
248+
....: [(1,1), (-1,-1), (1,0),
249+
....: (-1,0), (1,-1), (-2,1)]).vertices()
250+
(A vertex at (-25, 0, 0),
251+
A vertex at (-15, 50, -60),
252+
A vertex at (0, -25, 0),
253+
A vertex at (0, 0, -25),
254+
A vertex at (16, -35, 54),
255+
A vertex at (24, 10, 31))
256+
sage: gale_transform_to_polytope(
257+
....: [(1,1), (-1,-1), (1,0),
258+
....: (-1,0), (1,-1), (-2,1)],
259+
....: base_ring=RDF).vertices()
260+
(A vertex at (-0.64, 1.4, -2.16),
261+
A vertex at (-0.96, -0.4, -1.24),
262+
A vertex at (0.6, -2.0, 2.4),
263+
A vertex at (1.0, 0.0, 0.0),
264+
A vertex at (0.0, 1.0, 0.0),
265+
A vertex at (0.0, 0.0, 1.0))
266+
267+
One can also specify the backend::
268+
269+
sage: gale_transform_to_polytope(
270+
....: [(1,1), (-1,-1), (1,0),
271+
....: (-1,0), (1,-1), (-2,1)],
272+
....: backend='field').backend()
273+
'field'
274+
sage: gale_transform_to_polytope(
275+
....: [(1,1), (-1,-1), (1,0),
276+
....: (-1,0), (1,-1), (-2,1)],
277+
....: backend='cdd', base_ring=RDF).backend()
278+
'cdd'
279+
280+
A gale transform corresponds to a polytope if and only if
281+
every oriented (linear) hyperplane
282+
has at least two vectors on each side.
283+
See Theorem 6.19 of [Zie2007]_.
284+
If this is not the case, one of two errors is raised.
285+
286+
If there is such a hyperplane with no vector on one side,
287+
the vectors are not totally cyclic::
288+
289+
sage: gale_transform_to_polytope([(0,1), (1,1), (1,0), (-1,0)])
290+
Traceback (most recent call last):
291+
...
292+
ValueError: input vectors not totally cyclic
293+
294+
If every hyperplane has at least one vector on each side, then the gale
295+
transform corresponds to a point configuration.
296+
It corresponds to a polytope if and only if this point configuration is
297+
convex and if and only if every hyperplane contains at least two vectors of
298+
the gale transform on each side.
299+
300+
If this is not the case, an error is raised::
301+
302+
sage: gale_transform_to_polytope([(0,1), (1,1), (1,0), (-1,-1)])
303+
Traceback (most recent call last):
304+
...
305+
ValueError: the gale transform does not correspond to a polytope
306+
307+
TESTS::
308+
309+
sage: def test(P):
310+
....: P1 = gale_transform_to_polytope(
311+
....: P.gale_transform(), base_ring=P.base_ring(),
312+
....: backend=P.backend())
313+
....: assert P1.is_combinatorially_isomorphic(P)
314+
315+
sage: test(polytopes.cube())
316+
sage: test(polytopes.permutahedron(4))
317+
sage: test(polytopes.regular_polygon(5))
318+
sage: test(polytopes.regular_polygon(7, exact=False))
319+
sage: test(polytopes.snub_cube(exact=True, backend='normaliz')) # optional - pynormaliz
320+
"""
321+
vertices = gale_transform_to_primal(vectors, base_ring, backend)
322+
P = Polyhedron(vertices=vertices, base_ring=base_ring, backend=backend)
323+
324+
if not P.n_vertices() == len(vertices):
325+
# If the input vectors are not totally cyclic, ``gale_transform_to_primal``
326+
# raises an error.
327+
# As no error was raised so far, the gale transform corresponds to
328+
# to a point configuration.
329+
# It corresponds to a polytope if and only if
330+
# ``vertices`` are in convex position.
331+
raise ValueError("the gale transform does not correspond to a polytope")
332+
333+
return P
334+
335+
def gale_transform_to_primal(vectors, base_ring=None, backend=None):
336+
r"""
337+
Return a point configuration dual to a totally cyclic vector configuration.
338+
339+
This is the dehomogenized vector configuration dual to the input.
340+
The dual vector configuration is acyclic and can therefore
341+
be dehomogenized as the input is totally cyclic.
342+
343+
INPUT:
344+
345+
- ``vectors`` -- the ordered vectors of the Gale transform
346+
347+
- ``base_ring`` -- string (default: `None`);
348+
the base ring to be used for the construction
349+
350+
- ``backend`` -- string (default: `None`);
351+
the backend to be use to construct a polyhedral,
352+
used interally in case the center is not the origin,
353+
see :func:`~sage.geometry.polyhedron.constructor.Polyhedron`
354+
355+
OUTPUT: An ordered point configuration as list of vectors.
356+
357+
.. NOTE::
358+
359+
If the center of the (input) vectors is the origin,
360+
the function is much faster and might give a nicer representation
361+
of the point configuration.
362+
363+
If this is not the case, the vectors will be scaled
364+
(each by a positive scalar) accordingly.
365+
366+
ALGORITHM:
367+
368+
Step 1: If the center of the (input) vectors is not the origin,
369+
we do an appropriate transformation to make it so.
370+
371+
Step 2: We add a row of ones on top of ``Matrix(vectors)``.
372+
The right kernel of this larger matrix is the dual configuration space,
373+
and a basis of this space provides the dual point configuration.
374+
375+
More concretely, the dual vector configuration (inhomogeneous)
376+
is obtained by taking a basis of the right kernel of ``Matrix(vectors)``.
377+
If the center of the (input) vectors is the origin,
378+
there exists a basis of the right kernel of the form
379+
``[[1], [V]]``, where ``[1]`` represents a row of ones.
380+
Then, ``V`` is a dehomogenization and thus the dual point configuration.
381+
382+
To extend ``[1]`` to a basis of ``Matrix(vectors)``, we add
383+
a row of ones to ``Matrix(vectors)`` and calculate a basis of the
384+
right kernel of the obtained matrix.
385+
386+
REFERENCES:
387+
388+
For more information, see Section 6.4 of [Zie2007]_
389+
or Definition 2.5.1 and Definition 4.1.35 of [DLRS2010]_.
390+
391+
.. SEEALSO::
392+
393+
:func`~sage.geometry.polyhedron.library.gale_transform_to_polytope`.
394+
395+
EXAMPLES::
396+
397+
sage: from sage.geometry.polyhedron.library import gale_transform_to_primal
398+
sage: points = ((0, -1), (-1, 0), (1, 1), (1, 1), (-1, 0), (0, -1))
399+
sage: gale_transform_to_primal(points)
400+
[(0, 0, 1), (0, 1, 0), (1, 0, 0), (-1, 0, 0), (0, -1, 0), (0, 0, -1)]
401+
402+
One can specify the base ring::
403+
404+
sage: gale_transform_to_primal(
405+
....: [(1,1), (-1,-1), (1,0),
406+
....: (-1,0), (1,-1), (-2,1)])
407+
[(16, -35, 54),
408+
(24, 10, 31),
409+
(-15, 50, -60),
410+
(-25, 0, 0),
411+
(0, -25, 0),
412+
(0, 0, -25)]
413+
sage: gale_transform_to_primal(
414+
....: [(1,1),(-1,-1),(1,0),(-1,0),(1,-1),(-2,1)], base_ring=RDF)
415+
[(-0.6400000000000001, 1.4, -2.1600000000000006),
416+
(-0.9600000000000002, -0.39999999999999997, -1.2400000000000002),
417+
(0.6000000000000001, -2.0, 2.4000000000000004),
418+
(1.0, 0.0, 0.0),
419+
(0.0, 1.0, 0.0),
420+
(0.0, 0.0, 1.0)]
421+
422+
One can also specify the backend to be used internally::
423+
424+
sage: gale_transform_to_primal(
425+
....: [(1,1), (-1,-1), (1,0),
426+
....: (-1,0), (1,-1), (-2,1)], backend='field')
427+
[(48, -71, 88),
428+
(84, -28, 99),
429+
(-77, 154, -132),
430+
(-55, 0, 0),
431+
(0, -55, 0),
432+
(0, 0, -55)]
433+
sage: gale_transform_to_primal( # optional - pynormaliz
434+
....: [(1,1), (-1,-1), (1,0),
435+
....: (-1,0), (1,-1), (-2,1)], backend='normaliz')
436+
[(16, -35, 54),
437+
(24, 10, 31),
438+
(-15, 50, -60),
439+
(-25, 0, 0),
440+
(0, -25, 0),
441+
(0, 0, -25)]
442+
443+
The input vectors should be totally cyclic::
444+
445+
sage: gale_transform_to_primal([(0,1), (1,0), (1,1), (-1,0)])
446+
Traceback (most recent call last):
447+
...
448+
ValueError: input vectors not totally cyclic
449+
450+
sage: gale_transform_to_primal(
451+
....: [(1,1,0), (-1,-1,0), (1,0,0),
452+
....: (-1,0,0), (1,-1,0), (-2,1,0)], backend='field')
453+
Traceback (most recent call last):
454+
...
455+
ValueError: input vectors not totally cyclic
456+
"""
457+
from sage.modules.free_module_element import vector
458+
from sage.matrix.all import Matrix
459+
if base_ring:
460+
vectors = tuple(vector(base_ring, x) for x in vectors)
461+
else:
462+
vectors = tuple(vector(x) for x in vectors)
463+
464+
if not sum(vectors).is_zero():
465+
# The center of the input vectors shall be the origin.
466+
# If this is not the case, we scale them accordingly.
467+
# This has the adventage that right kernel of ``vectors`` can be
468+
# presented in the form ``[[1], [V]]``, where ``V`` are the points
469+
# in the dual point configuration.
470+
# (Dehomogenization is straightforward.)
471+
472+
# Scaling of the vectors is equivalent to finding a hyperplane that intersects
473+
# all vectors of the dual point configuration. But if the input is already provided
474+
# such that the vectors add up to zero, the coordinates might be nicer.
475+
# (And this is faster.)
476+
477+
if base_ring:
478+
ker = Matrix(base_ring, vectors).left_kernel()
479+
else:
480+
ker = Matrix(vectors).left_kernel()
481+
solutions = Polyhedron(lines=tuple(y for y in ker.basis_matrix()), base_ring=base_ring, backend=backend)
482+
483+
from sage.matrix.special import identity_matrix
484+
pos_orthant = Polyhedron(rays=identity_matrix(len(vectors)), base_ring=base_ring, backend=backend)
485+
pos_solutions = solutions.intersection(pos_orthant)
486+
if base_ring is ZZ:
487+
pos_solutions = pos_solutions.change_ring(ZZ)
488+
489+
# Any integral point in ``pos_solutions`` will correspond to scaling-factors
490+
# that make ``sum(vectors)`` zero.
491+
x = pos_solutions.representative_point()
492+
if not all(y > 0 for y in x):
493+
raise ValueError("input vectors not totally cyclic")
494+
vectors = tuple(vec*x[i] for i,vec in enumerate(vectors))
495+
496+
# The right kernel of ``vectors`` has a basis of the form ``[[1], [V]]``,
497+
# where ``V`` is the dehomogenized dual point configuration.
498+
# If we append a row of ones to ``vectors``, ``V`` is just the right kernel.
499+
if base_ring:
500+
m = Matrix(base_ring, vectors).transpose().stack(Matrix(base_ring, [[1]*len(vectors)]))
501+
else:
502+
m = Matrix(vectors).transpose().stack(Matrix([[1]*len(vectors)]))
503+
504+
if m.rank() != len(vectors[0]) + 1:
505+
# The given vectors do not span the ambient space,
506+
# then there exists a nonnegative value vector.
507+
raise ValueError("input vectors not totally cyclic")
508+
509+
return m.right_kernel_matrix(basis='computed').columns()
510+
511+
196512
class Polytopes():
197513
"""
198514
A class of constructors for commonly used, famous, or interesting

0 commit comments

Comments
 (0)