passagemath-polyhedra 10.6.31rc3__cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of passagemath-polyhedra might be problematic. Click here for more details.
- passagemath_polyhedra-10.6.31rc3.dist-info/METADATA +367 -0
- passagemath_polyhedra-10.6.31rc3.dist-info/METADATA.bak +369 -0
- passagemath_polyhedra-10.6.31rc3.dist-info/RECORD +206 -0
- passagemath_polyhedra-10.6.31rc3.dist-info/WHEEL +6 -0
- passagemath_polyhedra-10.6.31rc3.dist-info/top_level.txt +2 -0
- passagemath_polyhedra.libs/libgmp-6e109695.so.10.5.0 +0 -0
- passagemath_polyhedra.libs/libgomp-e985bcbb.so.1.0.0 +0 -0
- sage/all__sagemath_polyhedra.py +50 -0
- sage/game_theory/all.py +8 -0
- sage/game_theory/catalog.py +6 -0
- sage/game_theory/catalog_normal_form_games.py +923 -0
- sage/game_theory/cooperative_game.py +844 -0
- sage/game_theory/matching_game.py +1181 -0
- sage/game_theory/normal_form_game.py +2697 -0
- sage/game_theory/parser.py +275 -0
- sage/geometry/all__sagemath_polyhedra.py +22 -0
- sage/geometry/cone.py +6940 -0
- sage/geometry/cone_catalog.py +847 -0
- sage/geometry/cone_critical_angles.py +1027 -0
- sage/geometry/convex_set.py +1119 -0
- sage/geometry/fan.py +3743 -0
- sage/geometry/fan_isomorphism.py +389 -0
- sage/geometry/fan_morphism.py +1884 -0
- sage/geometry/hasse_diagram.py +202 -0
- sage/geometry/hyperplane_arrangement/affine_subspace.py +390 -0
- sage/geometry/hyperplane_arrangement/all.py +1 -0
- sage/geometry/hyperplane_arrangement/arrangement.py +3895 -0
- sage/geometry/hyperplane_arrangement/check_freeness.py +145 -0
- sage/geometry/hyperplane_arrangement/hyperplane.py +773 -0
- sage/geometry/hyperplane_arrangement/library.py +825 -0
- sage/geometry/hyperplane_arrangement/ordered_arrangement.py +642 -0
- sage/geometry/hyperplane_arrangement/plot.py +520 -0
- sage/geometry/integral_points.py +35 -0
- sage/geometry/integral_points_generic_dense.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/geometry/integral_points_generic_dense.pyx +7 -0
- sage/geometry/lattice_polytope.py +5894 -0
- sage/geometry/linear_expression.py +773 -0
- sage/geometry/newton_polygon.py +767 -0
- sage/geometry/point_collection.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/geometry/point_collection.pyx +1008 -0
- sage/geometry/polyhedral_complex.py +2616 -0
- sage/geometry/polyhedron/all.py +8 -0
- sage/geometry/polyhedron/backend_cdd.py +460 -0
- sage/geometry/polyhedron/backend_cdd_rdf.py +231 -0
- sage/geometry/polyhedron/backend_field.py +347 -0
- sage/geometry/polyhedron/backend_normaliz.py +2503 -0
- sage/geometry/polyhedron/backend_number_field.py +168 -0
- sage/geometry/polyhedron/backend_polymake.py +765 -0
- sage/geometry/polyhedron/backend_ppl.py +582 -0
- sage/geometry/polyhedron/base.py +1206 -0
- sage/geometry/polyhedron/base0.py +1444 -0
- sage/geometry/polyhedron/base1.py +886 -0
- sage/geometry/polyhedron/base2.py +812 -0
- sage/geometry/polyhedron/base3.py +1845 -0
- sage/geometry/polyhedron/base4.py +1262 -0
- sage/geometry/polyhedron/base5.py +2700 -0
- sage/geometry/polyhedron/base6.py +1741 -0
- sage/geometry/polyhedron/base7.py +997 -0
- sage/geometry/polyhedron/base_QQ.py +1258 -0
- sage/geometry/polyhedron/base_RDF.py +98 -0
- sage/geometry/polyhedron/base_ZZ.py +934 -0
- sage/geometry/polyhedron/base_mutable.py +215 -0
- sage/geometry/polyhedron/base_number_field.py +122 -0
- sage/geometry/polyhedron/cdd_file_format.py +155 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/all.py +1 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/base.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +76 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +3859 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd +39 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +1038 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/conversions.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pxd +9 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx +501 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd +207 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd +102 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +2274 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd +370 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx +84 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd +31 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx +587 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd +52 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +560 -0
- sage/geometry/polyhedron/constructor.py +773 -0
- sage/geometry/polyhedron/double_description.py +753 -0
- sage/geometry/polyhedron/double_description_inhomogeneous.py +564 -0
- sage/geometry/polyhedron/face.py +1060 -0
- sage/geometry/polyhedron/generating_function.py +1810 -0
- sage/geometry/polyhedron/lattice_euclidean_group_element.py +178 -0
- sage/geometry/polyhedron/library.py +3502 -0
- sage/geometry/polyhedron/misc.py +121 -0
- sage/geometry/polyhedron/modules/all.py +1 -0
- sage/geometry/polyhedron/modules/formal_polyhedra_module.py +155 -0
- sage/geometry/polyhedron/palp_database.py +447 -0
- sage/geometry/polyhedron/parent.py +1279 -0
- sage/geometry/polyhedron/plot.py +1986 -0
- sage/geometry/polyhedron/ppl_lattice_polygon.py +556 -0
- sage/geometry/polyhedron/ppl_lattice_polytope.py +1257 -0
- sage/geometry/polyhedron/representation.py +1723 -0
- sage/geometry/pseudolines.py +515 -0
- sage/geometry/relative_interior.py +445 -0
- sage/geometry/toric_plotter.py +1103 -0
- sage/geometry/triangulation/all.py +2 -0
- sage/geometry/triangulation/base.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/geometry/triangulation/base.pyx +963 -0
- sage/geometry/triangulation/data.h +147 -0
- sage/geometry/triangulation/data.pxd +4 -0
- sage/geometry/triangulation/element.py +914 -0
- sage/geometry/triangulation/functions.h +10 -0
- sage/geometry/triangulation/functions.pxd +4 -0
- sage/geometry/triangulation/point_configuration.py +2256 -0
- sage/geometry/triangulation/triangulations.h +49 -0
- sage/geometry/triangulation/triangulations.pxd +7 -0
- sage/geometry/voronoi_diagram.py +319 -0
- sage/interfaces/all__sagemath_polyhedra.py +1 -0
- sage/interfaces/polymake.py +2028 -0
- sage/numerical/all.py +13 -0
- sage/numerical/all__sagemath_polyhedra.py +11 -0
- sage/numerical/backends/all.py +1 -0
- sage/numerical/backends/all__sagemath_polyhedra.py +1 -0
- sage/numerical/backends/cvxopt_backend.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/backends/cvxopt_backend.pyx +1006 -0
- sage/numerical/backends/cvxopt_backend_test.py +19 -0
- sage/numerical/backends/cvxopt_sdp_backend.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/backends/cvxopt_sdp_backend.pyx +382 -0
- sage/numerical/backends/cvxpy_backend.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/backends/cvxpy_backend.pxd +41 -0
- sage/numerical/backends/cvxpy_backend.pyx +934 -0
- sage/numerical/backends/cvxpy_backend_test.py +13 -0
- sage/numerical/backends/generic_backend_test.py +24 -0
- sage/numerical/backends/interactivelp_backend.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/backends/interactivelp_backend.pxd +36 -0
- sage/numerical/backends/interactivelp_backend.pyx +1231 -0
- sage/numerical/backends/interactivelp_backend_test.py +12 -0
- sage/numerical/backends/logging_backend.py +391 -0
- sage/numerical/backends/matrix_sdp_backend.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/backends/matrix_sdp_backend.pxd +15 -0
- sage/numerical/backends/matrix_sdp_backend.pyx +478 -0
- sage/numerical/backends/ppl_backend.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/backends/ppl_backend.pyx +1126 -0
- sage/numerical/backends/ppl_backend_test.py +13 -0
- sage/numerical/backends/scip_backend.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/backends/scip_backend.pxd +22 -0
- sage/numerical/backends/scip_backend.pyx +1289 -0
- sage/numerical/backends/scip_backend_test.py +13 -0
- sage/numerical/interactive_simplex_method.py +5338 -0
- sage/numerical/knapsack.py +665 -0
- sage/numerical/linear_functions.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/linear_functions.pxd +31 -0
- sage/numerical/linear_functions.pyx +1648 -0
- sage/numerical/linear_tensor.py +470 -0
- sage/numerical/linear_tensor_constraints.py +448 -0
- sage/numerical/linear_tensor_element.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/linear_tensor_element.pxd +6 -0
- sage/numerical/linear_tensor_element.pyx +459 -0
- sage/numerical/mip.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/mip.pxd +40 -0
- sage/numerical/mip.pyx +3667 -0
- sage/numerical/sdp.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/numerical/sdp.pxd +39 -0
- sage/numerical/sdp.pyx +1433 -0
- sage/rings/all__sagemath_polyhedra.py +3 -0
- sage/rings/polynomial/all__sagemath_polyhedra.py +10 -0
- sage/rings/polynomial/omega.py +982 -0
- sage/schemes/all__sagemath_polyhedra.py +2 -0
- sage/schemes/toric/all.py +10 -0
- sage/schemes/toric/chow_group.py +1248 -0
- sage/schemes/toric/divisor.py +2082 -0
- sage/schemes/toric/divisor_class.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/schemes/toric/divisor_class.pyx +322 -0
- sage/schemes/toric/fano_variety.py +1606 -0
- sage/schemes/toric/homset.py +650 -0
- sage/schemes/toric/ideal.py +451 -0
- sage/schemes/toric/library.py +1322 -0
- sage/schemes/toric/morphism.py +1958 -0
- sage/schemes/toric/points.py +1032 -0
- sage/schemes/toric/sheaf/all.py +1 -0
- sage/schemes/toric/sheaf/constructor.py +302 -0
- sage/schemes/toric/sheaf/klyachko.py +921 -0
- sage/schemes/toric/toric_subscheme.py +905 -0
- sage/schemes/toric/variety.py +3460 -0
- sage/schemes/toric/weierstrass.py +1078 -0
- sage/schemes/toric/weierstrass_covering.py +457 -0
- sage/schemes/toric/weierstrass_higher.py +288 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.info +10 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v03 +0 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v04 +0 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v05 +1 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v06 +1 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.info +22 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v04 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v05 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v06 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v07 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v08 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v09 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v10 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v11 +1 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v12 +1 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v13 +1 -0
- sage_wheels/share/reflexive_polytopes/reflexive_polytopes_2d +80 -0
- sage_wheels/share/reflexive_polytopes/reflexive_polytopes_3d +37977 -0
|
@@ -0,0 +1,3859 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-polyhedra
|
|
2
|
+
r"""
|
|
3
|
+
Combinatorial polyhedron
|
|
4
|
+
|
|
5
|
+
This module gathers algorithms for polyhedra that only depend on the
|
|
6
|
+
vertex-facet incidences and that are called combinatorial polyhedron.
|
|
7
|
+
The main class is :class:`CombinatorialPolyhedron`. Most importantly,
|
|
8
|
+
this class allows to iterate quickly through the faces (possibly
|
|
9
|
+
of given dimension) via the :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator` object. The :class:`CombinatorialPolyhedron`
|
|
10
|
+
uses this iterator to quickly generate the f-vector, the edges,
|
|
11
|
+
the ridges and the face lattice.
|
|
12
|
+
|
|
13
|
+
Terminology used in this module:
|
|
14
|
+
|
|
15
|
+
- Vrep -- ``[vertices, rays, lines]`` of the polyhedron
|
|
16
|
+
- Hrep -- inequalities and equations of the polyhedron
|
|
17
|
+
- Facets -- facets of the polyhedron
|
|
18
|
+
- Vrepresentation -- represents a face by the list of Vrep it contains
|
|
19
|
+
- Hrepresentation -- represents a face by a list of Hrep it is contained in
|
|
20
|
+
- bit representation -- represents incidences as bitset, where each bit
|
|
21
|
+
represents one incidence. There might be trailing zeros, to fit alignment
|
|
22
|
+
requirements. In most instances, faces are represented by the bit
|
|
23
|
+
representation, where each bit corresponds to a Vrep or facet. Thus a bit
|
|
24
|
+
representation can either be a Vrep or facet representation depending on
|
|
25
|
+
context.
|
|
26
|
+
|
|
27
|
+
EXAMPLES:
|
|
28
|
+
|
|
29
|
+
Construction::
|
|
30
|
+
|
|
31
|
+
sage: P = polytopes.hypercube(4)
|
|
32
|
+
sage: C = CombinatorialPolyhedron(P); C
|
|
33
|
+
A 4-dimensional combinatorial polyhedron with 8 facets
|
|
34
|
+
|
|
35
|
+
Obtaining edges and ridges::
|
|
36
|
+
|
|
37
|
+
sage: C.edges()[:2]
|
|
38
|
+
((A vertex at (1, -1, -1, -1), A vertex at (-1, -1, -1, -1)),
|
|
39
|
+
(A vertex at (-1, -1, -1, 1), A vertex at (-1, -1, -1, -1)))
|
|
40
|
+
sage: C.edges(names=False)[:2]
|
|
41
|
+
((6, 15), (14, 15))
|
|
42
|
+
|
|
43
|
+
sage: C.ridges()[:2]
|
|
44
|
+
((An inequality (0, 0, 1, 0) x + 1 >= 0,
|
|
45
|
+
An inequality (0, 1, 0, 0) x + 1 >= 0),
|
|
46
|
+
(An inequality (0, 0, 0, 1) x + 1 >= 0,
|
|
47
|
+
An inequality (0, 1, 0, 0) x + 1 >= 0))
|
|
48
|
+
sage: C.ridges(names=False)[:2]
|
|
49
|
+
((6, 7), (5, 7))
|
|
50
|
+
|
|
51
|
+
Vertex-graph and facet-graph::
|
|
52
|
+
|
|
53
|
+
sage: C.vertex_graph() # needs sage.graphs
|
|
54
|
+
Graph on 16 vertices
|
|
55
|
+
sage: C.facet_graph() # needs sage.graphs
|
|
56
|
+
Graph on 8 vertices
|
|
57
|
+
|
|
58
|
+
Face lattice::
|
|
59
|
+
|
|
60
|
+
sage: C.face_lattice() # needs sage.combinat
|
|
61
|
+
Finite lattice containing 82 elements
|
|
62
|
+
|
|
63
|
+
Face iterator::
|
|
64
|
+
|
|
65
|
+
sage: C.face_generator()
|
|
66
|
+
Iterator over the proper faces of a 4-dimensional combinatorial polyhedron
|
|
67
|
+
|
|
68
|
+
sage: C.face_generator(2)
|
|
69
|
+
Iterator over the 2-faces of a 4-dimensional combinatorial polyhedron
|
|
70
|
+
|
|
71
|
+
AUTHOR:
|
|
72
|
+
|
|
73
|
+
- Jonathan Kliem (2019-04)
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# ****************************************************************************
|
|
77
|
+
# Copyright (C) 2019 Jonathan Kliem <jonathan.kliem@gmail.com>
|
|
78
|
+
#
|
|
79
|
+
# This program is free software: you can redistribute it and/or modify
|
|
80
|
+
# it under the terms of the GNU General Public License as published by
|
|
81
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
82
|
+
# (at your option) any later version.
|
|
83
|
+
# https://www.gnu.org/licenses/
|
|
84
|
+
# ****************************************************************************
|
|
85
|
+
|
|
86
|
+
import numbers
|
|
87
|
+
from memory_allocator cimport MemoryAllocator
|
|
88
|
+
from cysignals.memory cimport check_calloc, sig_free
|
|
89
|
+
|
|
90
|
+
import sage.geometry.abc
|
|
91
|
+
|
|
92
|
+
from sage.matrix.matrix_dense cimport Matrix_dense
|
|
93
|
+
from sage.misc.misc import is_iterator
|
|
94
|
+
from sage.structure.element import Matrix
|
|
95
|
+
from .conversions import (incidence_matrix_to_bit_rep_of_facets,
|
|
96
|
+
incidence_matrix_to_bit_rep_of_Vrep,
|
|
97
|
+
facets_tuple_to_bit_rep_of_facets,
|
|
98
|
+
facets_tuple_to_bit_rep_of_Vrep)
|
|
99
|
+
from sage.geometry.polyhedron.combinatorial_polyhedron.conversions cimport Vrep_list_to_bit_rep
|
|
100
|
+
from sage.misc.cachefunc import cached_method
|
|
101
|
+
|
|
102
|
+
from sage.rings.integer cimport smallInteger
|
|
103
|
+
from cysignals.signals cimport sig_check
|
|
104
|
+
|
|
105
|
+
from sage.geometry.polyhedron.combinatorial_polyhedron.face_data_structure cimport face_len_atoms, face_init, face_free
|
|
106
|
+
from sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator cimport iter_t, parallel_f_vector
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
cdef extern from "Python.h":
|
|
110
|
+
int unlikely(int) nogil # Defined by Cython
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
cdef class CombinatorialPolyhedron(SageObject):
|
|
114
|
+
r"""
|
|
115
|
+
The class of the Combinatorial Type of a Polyhedron, a Polytope.
|
|
116
|
+
|
|
117
|
+
INPUT:
|
|
118
|
+
|
|
119
|
+
- ``data`` -- an instance of
|
|
120
|
+
* :class:`~sage.geometry.polyhedron.parent.Polyhedron_base`
|
|
121
|
+
* or a :class:`~sage.geometry.lattice_polytope.LatticePolytopeClass`
|
|
122
|
+
* or a :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone`
|
|
123
|
+
* or an ``incidence_matrix`` as in
|
|
124
|
+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.incidence_matrix`
|
|
125
|
+
In this case you should also specify the ``Vrep`` and ``facets`` arguments
|
|
126
|
+
* or list of facets, each facet given as
|
|
127
|
+
a list of ``[vertices, rays, lines]`` if the polyhedron is unbounded,
|
|
128
|
+
then rays and lines and the extra argument ``nr_lines`` are required
|
|
129
|
+
if the polyhedron contains no lines, the rays can be thought of
|
|
130
|
+
as the vertices of the facets deleted from a bounded polyhedron see
|
|
131
|
+
:class:`~sage.geometry.polyhedron.parent.Polyhedron_base` on how to use
|
|
132
|
+
rays and lines
|
|
133
|
+
* or an integer, representing the dimension of a polyhedron equal to its
|
|
134
|
+
affine hull
|
|
135
|
+
* or a tuple consisting of facets and vertices as two
|
|
136
|
+
:class:`~sage.geometry.polyhedron.combinatorial_polyhedron.list_of_faces.ListOfFaces`.
|
|
137
|
+
- ``Vrep`` -- (optional) when ``data`` is an incidence matrix, it should
|
|
138
|
+
be the list of ``[vertices, rays, lines]``, if the rows in the incidence_matrix
|
|
139
|
+
should correspond to names
|
|
140
|
+
- ``facets`` -- (optional) when ``data`` is an incidence matrix or a list of facets,
|
|
141
|
+
it should be a list of facets that would be used instead of indices (of the columns
|
|
142
|
+
of the incidence matrix).
|
|
143
|
+
- ``unbounded`` -- value will be overwritten if ``data`` is a polyhedron;
|
|
144
|
+
if ``unbounded`` and ``data`` is incidence matrix or a list of facets,
|
|
145
|
+
need to specify ``far_face``
|
|
146
|
+
- ``far_face`` -- (semi-optional); if the polyhedron is unbounded this
|
|
147
|
+
needs to be set to the list of indices of the rays and line unless ``data`` is
|
|
148
|
+
an instance of :class:`~sage.geometry.polyhedron.parent.Polyhedron_base`.
|
|
149
|
+
|
|
150
|
+
EXAMPLES:
|
|
151
|
+
|
|
152
|
+
We illustrate all possible input: a polyhedron:
|
|
153
|
+
|
|
154
|
+
sage: P = polytopes.cube()
|
|
155
|
+
sage: CombinatorialPolyhedron(P)
|
|
156
|
+
A 3-dimensional combinatorial polyhedron with 6 facets
|
|
157
|
+
|
|
158
|
+
a lattice polytope::
|
|
159
|
+
|
|
160
|
+
sage: points = [(1,0,0), (0,1,0), (0,0,1),
|
|
161
|
+
....: (-1,0,0), (0,-1,0), (0,0,-1)]
|
|
162
|
+
sage: L = LatticePolytope(points)
|
|
163
|
+
sage: CombinatorialPolyhedron(L)
|
|
164
|
+
A 3-dimensional combinatorial polyhedron with 8 facets
|
|
165
|
+
|
|
166
|
+
a cone::
|
|
167
|
+
|
|
168
|
+
sage: M = Cone([(1,0), (0,1)])
|
|
169
|
+
sage: CombinatorialPolyhedron(M)
|
|
170
|
+
A 2-dimensional combinatorial polyhedron with 2 facets
|
|
171
|
+
|
|
172
|
+
an incidence matrix::
|
|
173
|
+
|
|
174
|
+
sage: P = Polyhedron(rays=[[0,1]])
|
|
175
|
+
sage: data = P.incidence_matrix()
|
|
176
|
+
sage: far_face = [i for i in range(2) if not P.Vrepresentation()[i].is_vertex()]
|
|
177
|
+
sage: CombinatorialPolyhedron(data, unbounded=True, far_face=far_face)
|
|
178
|
+
A 1-dimensional combinatorial polyhedron with 1 facet
|
|
179
|
+
sage: C = CombinatorialPolyhedron(data, Vrep=['myvertex'],
|
|
180
|
+
....: facets=['myfacet'], unbounded=True, far_face=far_face)
|
|
181
|
+
sage: C.Vrepresentation()
|
|
182
|
+
('myvertex',)
|
|
183
|
+
sage: C.Hrepresentation()
|
|
184
|
+
('myfacet',)
|
|
185
|
+
|
|
186
|
+
a list of facets::
|
|
187
|
+
|
|
188
|
+
sage: CombinatorialPolyhedron(((1,2,3),(1,2,4),(1,3,4),(2,3,4)))
|
|
189
|
+
A 3-dimensional combinatorial polyhedron with 4 facets
|
|
190
|
+
sage: facetnames = ['facet0', 'facet1', 'facet2', 'myfacet3']
|
|
191
|
+
sage: facetinc = ((1,2,3),(1,2,4),(1,3,4),(2,3,4))
|
|
192
|
+
sage: C = CombinatorialPolyhedron(facetinc, facets=facetnames)
|
|
193
|
+
sage: C.Vrepresentation()
|
|
194
|
+
(1, 2, 3, 4)
|
|
195
|
+
sage: C.Hrepresentation()
|
|
196
|
+
('facet0', 'facet1', 'facet2', 'myfacet3')
|
|
197
|
+
|
|
198
|
+
an integer::
|
|
199
|
+
|
|
200
|
+
sage: CombinatorialPolyhedron(-1).f_vector()
|
|
201
|
+
(1)
|
|
202
|
+
sage: CombinatorialPolyhedron(0).f_vector()
|
|
203
|
+
(1, 1)
|
|
204
|
+
sage: CombinatorialPolyhedron(5).f_vector()
|
|
205
|
+
(1, 0, 0, 0, 0, 0, 1)
|
|
206
|
+
|
|
207
|
+
tuple of ``ListOfFaces``::
|
|
208
|
+
|
|
209
|
+
sage: from sage.geometry.polyhedron.combinatorial_polyhedron.conversions \
|
|
210
|
+
....: import facets_tuple_to_bit_rep_of_facets, \
|
|
211
|
+
....: facets_tuple_to_bit_rep_of_Vrep
|
|
212
|
+
sage: bi_pyr = ((0,1,4), (1,2,4), (2,3,4), (3,0,4),
|
|
213
|
+
....: (0,1,5), (1,2,5), (2,3,5), (3,0,5))
|
|
214
|
+
sage: facets = facets_tuple_to_bit_rep_of_facets(bi_pyr, 6)
|
|
215
|
+
sage: Vrep = facets_tuple_to_bit_rep_of_Vrep(bi_pyr, 6)
|
|
216
|
+
sage: C = CombinatorialPolyhedron((facets, Vrep)); C
|
|
217
|
+
A 3-dimensional combinatorial polyhedron with 8 facets
|
|
218
|
+
sage: C.f_vector()
|
|
219
|
+
(1, 6, 12, 8, 1)
|
|
220
|
+
|
|
221
|
+
Specifying that a polyhedron is unbounded is important. The following with a
|
|
222
|
+
polyhedron works fine::
|
|
223
|
+
|
|
224
|
+
sage: P = Polyhedron(ieqs=[[1,-1,0],[1,1,0]])
|
|
225
|
+
sage: C = CombinatorialPolyhedron(P) # this works fine
|
|
226
|
+
sage: C
|
|
227
|
+
A 2-dimensional combinatorial polyhedron with 2 facets
|
|
228
|
+
|
|
229
|
+
The following is incorrect, as ``unbounded`` is implicitly set to ``False``::
|
|
230
|
+
|
|
231
|
+
sage: data = P.incidence_matrix()
|
|
232
|
+
sage: vert = P.Vrepresentation()
|
|
233
|
+
sage: C = CombinatorialPolyhedron(data, Vrep=vert)
|
|
234
|
+
sage: C
|
|
235
|
+
A 2-dimensional combinatorial polyhedron with 2 facets
|
|
236
|
+
sage: C.f_vector()
|
|
237
|
+
Traceback (most recent call last):
|
|
238
|
+
...
|
|
239
|
+
ValueError: not all vertices are intersections of facets
|
|
240
|
+
sage: C.vertices()
|
|
241
|
+
(A line in the direction (0, 1), A vertex at (1, 0), A vertex at (-1, 0))
|
|
242
|
+
|
|
243
|
+
The correct usage is::
|
|
244
|
+
|
|
245
|
+
sage: far_face = [i for i in range(3) if not P.Vrepresentation()[i].is_vertex()]
|
|
246
|
+
sage: C = CombinatorialPolyhedron(data, Vrep=vert, unbounded=True, far_face=far_face)
|
|
247
|
+
sage: C
|
|
248
|
+
A 2-dimensional combinatorial polyhedron with 2 facets
|
|
249
|
+
sage: C.f_vector()
|
|
250
|
+
(1, 0, 2, 1)
|
|
251
|
+
sage: C.vertices()
|
|
252
|
+
()
|
|
253
|
+
|
|
254
|
+
TESTS:
|
|
255
|
+
|
|
256
|
+
Checking that :issue:`27987` is fixed::
|
|
257
|
+
|
|
258
|
+
sage: P1 = Polyhedron(vertices=[[0,1],[1,0]], rays=[[1,1]])
|
|
259
|
+
sage: P2 = Polyhedron(vertices=[[0,1],[1,0],[1,1]])
|
|
260
|
+
sage: P1.incidence_matrix() == P2.incidence_matrix()
|
|
261
|
+
True
|
|
262
|
+
sage: CombinatorialPolyhedron(P1).f_vector()
|
|
263
|
+
(1, 2, 3, 1)
|
|
264
|
+
sage: CombinatorialPolyhedron(P2).f_vector()
|
|
265
|
+
(1, 3, 3, 1)
|
|
266
|
+
sage: P1 = Polyhedron(vertices=[[0,1],[1,0]], rays=[[1,1]])
|
|
267
|
+
sage: P2 = Polyhedron(vertices=[[0,1],[1,0],[1,1]])
|
|
268
|
+
sage: CombinatorialPolyhedron(P1).f_vector()
|
|
269
|
+
(1, 2, 3, 1)
|
|
270
|
+
sage: CombinatorialPolyhedron(P2).f_vector()
|
|
271
|
+
(1, 3, 3, 1)
|
|
272
|
+
|
|
273
|
+
Some other tests regarding small polyhedra::
|
|
274
|
+
|
|
275
|
+
sage: P = Polyhedron(rays=[[1,0],[0,1]])
|
|
276
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
277
|
+
sage: C
|
|
278
|
+
A 2-dimensional combinatorial polyhedron with 2 facets
|
|
279
|
+
sage: C.f_vector()
|
|
280
|
+
(1, 1, 2, 1)
|
|
281
|
+
sage: C.vertices()
|
|
282
|
+
(A vertex at (0, 0),)
|
|
283
|
+
sage: data = P.incidence_matrix()
|
|
284
|
+
sage: vert = P.Vrepresentation()
|
|
285
|
+
sage: far_face = [i for i in range(3) if not P.Vrepresentation()[i].is_vertex()]
|
|
286
|
+
sage: C = CombinatorialPolyhedron(data, Vrep=vert, unbounded=True, far_face=far_face)
|
|
287
|
+
sage: C
|
|
288
|
+
A 2-dimensional combinatorial polyhedron with 2 facets
|
|
289
|
+
sage: C.f_vector()
|
|
290
|
+
(1, 1, 2, 1)
|
|
291
|
+
sage: C.vertices()
|
|
292
|
+
(A vertex at (0, 0),)
|
|
293
|
+
sage: CombinatorialPolyhedron(3r)
|
|
294
|
+
A 3-dimensional combinatorial polyhedron with 0 facets
|
|
295
|
+
|
|
296
|
+
Check that on wrong input subsequent calls of ``f_vector`` fail::
|
|
297
|
+
|
|
298
|
+
sage: data = P.incidence_matrix()
|
|
299
|
+
sage: vert = P.Vrepresentation()
|
|
300
|
+
sage: C = CombinatorialPolyhedron(data, Vrep=vert)
|
|
301
|
+
sage: C.f_vector()
|
|
302
|
+
Traceback (most recent call last):
|
|
303
|
+
...
|
|
304
|
+
ValueError: not all vertices are intersections of facets
|
|
305
|
+
sage: C.f_vector()
|
|
306
|
+
Traceback (most recent call last):
|
|
307
|
+
...
|
|
308
|
+
ValueError: not all vertices are intersections of facets
|
|
309
|
+
|
|
310
|
+
Check that :issue:`28678` is fixed::
|
|
311
|
+
|
|
312
|
+
sage: CombinatorialPolyhedron([])
|
|
313
|
+
A -1-dimensional combinatorial polyhedron with 0 facets
|
|
314
|
+
sage: CombinatorialPolyhedron(LatticePolytope([], lattice=ToricLattice(3)))
|
|
315
|
+
A -1-dimensional combinatorial polyhedron with 0 facets
|
|
316
|
+
"""
|
|
317
|
+
def __cinit__(self):
|
|
318
|
+
r"""
|
|
319
|
+
TESTS:
|
|
320
|
+
|
|
321
|
+
Not initializing the class, does not give segmentation fault::
|
|
322
|
+
|
|
323
|
+
sage: from sage.geometry.polyhedron.combinatorial_polyhedron.base import CombinatorialPolyhedron
|
|
324
|
+
sage: C = CombinatorialPolyhedron.__new__(CombinatorialPolyhedron)
|
|
325
|
+
sage: C.f_vector()
|
|
326
|
+
Traceback (most recent call last):
|
|
327
|
+
...
|
|
328
|
+
ValueError: the combinatorial polyhedron was not initialized
|
|
329
|
+
sage: C.face_lattice() # needs sage.combinat
|
|
330
|
+
Traceback (most recent call last):
|
|
331
|
+
...
|
|
332
|
+
ValueError: the combinatorial polyhedron was not initialized
|
|
333
|
+
sage: C.face_generator()
|
|
334
|
+
Traceback (most recent call last):
|
|
335
|
+
...
|
|
336
|
+
ValueError: the combinatorial polyhedron was not initialized
|
|
337
|
+
"""
|
|
338
|
+
# Note that all values are set to zero at the time ``__cinit__`` is called:
|
|
339
|
+
# https://cython.readthedocs.io/en/latest/src/userguide/special_methods.html#initialisation-methods
|
|
340
|
+
# In particular, ``__dealloc__`` will not do harm in this case.
|
|
341
|
+
|
|
342
|
+
self._dimension = -2 # a "NULL" value
|
|
343
|
+
self._equations = ()
|
|
344
|
+
self._all_faces = None
|
|
345
|
+
self._n_facets = -1
|
|
346
|
+
|
|
347
|
+
def __init__(self, data, Vrep=None, facets=None, unbounded=False, far_face=None):
|
|
348
|
+
r"""
|
|
349
|
+
Initialize :class:`CombinatorialPolyhedron`.
|
|
350
|
+
|
|
351
|
+
See :class:`CombinatorialPolyhedron`.
|
|
352
|
+
|
|
353
|
+
TESTS::
|
|
354
|
+
|
|
355
|
+
sage: C = CombinatorialPolyhedron([[0,1,2], [0,1,3], # indirect doctest
|
|
356
|
+
....: [0,2,3], [1,2,3]])
|
|
357
|
+
|
|
358
|
+
sage: TestSuite(sage.geometry.polyhedron.combinatorial_polyhedron.base.CombinatorialPolyhedron).run()
|
|
359
|
+
"""
|
|
360
|
+
self._equations = ()
|
|
361
|
+
self._far_face_tuple = ()
|
|
362
|
+
|
|
363
|
+
if isinstance(data, sage.geometry.abc.Polyhedron):
|
|
364
|
+
self._init_from_polyhedron(data)
|
|
365
|
+
return
|
|
366
|
+
if isinstance(data, sage.geometry.abc.LatticePolytope):
|
|
367
|
+
self._init_from_lattice_polytope(data)
|
|
368
|
+
return
|
|
369
|
+
if isinstance(data, sage.geometry.abc.ConvexRationalPolyhedralCone):
|
|
370
|
+
self._init_from_cone(data)
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
self._bounded = not unbounded
|
|
374
|
+
if unbounded:
|
|
375
|
+
if not far_face:
|
|
376
|
+
raise ValueError("must specify far face for unbounded polyhedron")
|
|
377
|
+
self._far_face_tuple = tuple(far_face)
|
|
378
|
+
|
|
379
|
+
if Vrep:
|
|
380
|
+
self._Vrep = tuple(Vrep)
|
|
381
|
+
|
|
382
|
+
self._init_facet_names(facets)
|
|
383
|
+
|
|
384
|
+
if data == [] or data == ():
|
|
385
|
+
self._init_as_trivial_polyhedron(-1)
|
|
386
|
+
elif isinstance(data, Matrix):
|
|
387
|
+
self._init_from_incidence_matrix(data)
|
|
388
|
+
elif isinstance(data, numbers.Integral):
|
|
389
|
+
self._init_as_trivial_polyhedron(data)
|
|
390
|
+
elif (isinstance(data, (tuple, list)) and
|
|
391
|
+
len(data) == 2 and
|
|
392
|
+
isinstance(data[0], ListOfFaces) and
|
|
393
|
+
isinstance(data[1], ListOfFaces)):
|
|
394
|
+
self._init_from_ListOfFaces(data[0], data[1])
|
|
395
|
+
|
|
396
|
+
else:
|
|
397
|
+
self._init_from_list_of_facets(data)
|
|
398
|
+
|
|
399
|
+
cdef _init_from_polyhedron(self, data):
|
|
400
|
+
r'''
|
|
401
|
+
Initialize from :class:`~sage.geometry.polyhedron.parent.Polyhedron_base`.
|
|
402
|
+
'''
|
|
403
|
+
self._Vrep = data.Vrepresentation()
|
|
404
|
+
self._facet_names = data.inequalities()
|
|
405
|
+
self._equations = data.equations()
|
|
406
|
+
self._dimension = data.dimension()
|
|
407
|
+
|
|
408
|
+
if not data.is_compact():
|
|
409
|
+
self._bounded = False
|
|
410
|
+
self._far_face_tuple = tuple(i for i in range(data.n_Vrepresentation()) if not data.Vrepresentation()[i].is_vertex())
|
|
411
|
+
else:
|
|
412
|
+
self._bounded = True
|
|
413
|
+
|
|
414
|
+
return self._init_from_incidence_matrix(data.incidence_matrix())
|
|
415
|
+
|
|
416
|
+
cdef _init_from_lattice_polytope(self, data):
|
|
417
|
+
r'''
|
|
418
|
+
Initialize from :class:`~sage.geometry.lattice_polytope.LatticePolytopeClass`.
|
|
419
|
+
'''
|
|
420
|
+
self._bounded = True
|
|
421
|
+
self._Vrep = tuple(data.vertices())
|
|
422
|
+
self._facet_names = tuple(data.facet_normals())
|
|
423
|
+
self._dimension = data.dimension()
|
|
424
|
+
return self._init_from_incidence_matrix(data.incidence_matrix())
|
|
425
|
+
|
|
426
|
+
cdef _init_from_cone(self, data):
|
|
427
|
+
r'''
|
|
428
|
+
Initialize from :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone`.
|
|
429
|
+
'''
|
|
430
|
+
self._bounded = False
|
|
431
|
+
self._Vrep = tuple(data.rays()) + (data.lattice().zero(),)
|
|
432
|
+
self._facet_names = tuple(data.facet_normals())
|
|
433
|
+
self._far_face_tuple = tuple(i for i in range(len(self._Vrep) - 1))
|
|
434
|
+
self._dimension = data.dim()
|
|
435
|
+
from sage.matrix.constructor import matrix
|
|
436
|
+
from sage.rings.integer_ring import ZZ
|
|
437
|
+
incidence_matrix = matrix(ZZ, data.incidence_matrix().rows()
|
|
438
|
+
+ [[ZZ.one() for _ in range(len(data.facet_normals()))]])
|
|
439
|
+
return self._init_from_incidence_matrix(incidence_matrix)
|
|
440
|
+
|
|
441
|
+
cdef _init_facet_names(self, facets):
|
|
442
|
+
'''
|
|
443
|
+
Store facet names and compute equations.
|
|
444
|
+
'''
|
|
445
|
+
if facets is not None:
|
|
446
|
+
facets = tuple(facets)
|
|
447
|
+
|
|
448
|
+
test = [1] * len(facets) # 0 if that facet is an equation
|
|
449
|
+
for i in range(len(facets)):
|
|
450
|
+
if hasattr(facets[i], "is_inequality"):
|
|
451
|
+
# We remove equations.
|
|
452
|
+
# At the moment only equations with this attribute ``True``
|
|
453
|
+
# will be detected.
|
|
454
|
+
if not facets[i].is_inequality():
|
|
455
|
+
test[i] = 0
|
|
456
|
+
self._facet_names = tuple(facets[i] for i in range(len(facets)) if test[i])
|
|
457
|
+
|
|
458
|
+
self._equations = tuple(facets[i] for i in range(len(facets)) if not test[i])
|
|
459
|
+
else:
|
|
460
|
+
self._facet_names = None
|
|
461
|
+
|
|
462
|
+
cdef _init_from_incidence_matrix(self, data):
|
|
463
|
+
"""
|
|
464
|
+
Initialize from an incidence matrix.
|
|
465
|
+
"""
|
|
466
|
+
# Input is incidence-matrix or was converted to it.
|
|
467
|
+
self._n_Hrepresentation = data.ncols()
|
|
468
|
+
self._n_Vrepresentation = data.nrows()
|
|
469
|
+
|
|
470
|
+
if not isinstance(data, Matrix_dense):
|
|
471
|
+
from sage.rings.integer_ring import ZZ
|
|
472
|
+
from sage.matrix.constructor import matrix
|
|
473
|
+
data = matrix(ZZ, data, sparse=False)
|
|
474
|
+
assert isinstance(data, Matrix_dense), "conversion to ``Matrix_dense`` didn't work"
|
|
475
|
+
|
|
476
|
+
# Store the incidence matrix.
|
|
477
|
+
if not data.is_immutable():
|
|
478
|
+
data = data.__copy__()
|
|
479
|
+
data.set_immutable()
|
|
480
|
+
self.incidence_matrix.set_cache(data)
|
|
481
|
+
|
|
482
|
+
# Delete equations.
|
|
483
|
+
data = data.delete_columns(
|
|
484
|
+
[i for i in range(data.ncols())
|
|
485
|
+
if all(data[j, i] for j in range(data.nrows()))],
|
|
486
|
+
check=False)
|
|
487
|
+
|
|
488
|
+
# Initializing the facets in their Bit-representation.
|
|
489
|
+
self._bitrep_facets = incidence_matrix_to_bit_rep_of_facets(data)
|
|
490
|
+
|
|
491
|
+
# Initializing the Vrep as their Bit-representation.
|
|
492
|
+
self._bitrep_Vrep = incidence_matrix_to_bit_rep_of_Vrep(data)
|
|
493
|
+
|
|
494
|
+
self._n_facets = self.bitrep_facets().n_faces()
|
|
495
|
+
|
|
496
|
+
self._initialize_far_face()
|
|
497
|
+
|
|
498
|
+
cdef _init_from_list_of_facets(self, data):
|
|
499
|
+
"""
|
|
500
|
+
Initialize from a list of facets.
|
|
501
|
+
|
|
502
|
+
Tuple and iterator work as well.
|
|
503
|
+
|
|
504
|
+
The facets are given by its ``[vertices, rays, lines]``.
|
|
505
|
+
"""
|
|
506
|
+
if is_iterator(data):
|
|
507
|
+
data = tuple(data)
|
|
508
|
+
|
|
509
|
+
if self._Vrep is None:
|
|
510
|
+
# Get the names of the Vrep.
|
|
511
|
+
Vrep = sorted(set.union(*map(set, data)))
|
|
512
|
+
n_Vrepresentation = len(Vrep)
|
|
513
|
+
if Vrep != range(len(Vrep)):
|
|
514
|
+
self._Vrep = tuple(Vrep)
|
|
515
|
+
Vinv = {v: i for i, v in enumerate(self._Vrep)}
|
|
516
|
+
else:
|
|
517
|
+
# Assuming the user gave as correct names for the vertices
|
|
518
|
+
# and labeled them instead by `0,...,n`.
|
|
519
|
+
n_Vrepresentation = len(self._Vrep)
|
|
520
|
+
|
|
521
|
+
self._n_Vrepresentation = n_Vrepresentation
|
|
522
|
+
|
|
523
|
+
# Relabel the Vrep to be `0,...,n`.
|
|
524
|
+
if self._Vrep is not None:
|
|
525
|
+
def f(v):
|
|
526
|
+
return Vinv[v]
|
|
527
|
+
else:
|
|
528
|
+
def f(v):
|
|
529
|
+
return int(v)
|
|
530
|
+
facets = tuple(tuple(f(i) for i in j) for j in data)
|
|
531
|
+
|
|
532
|
+
self._n_facets = len(facets)
|
|
533
|
+
self._n_Hrepresentation = len(facets)
|
|
534
|
+
|
|
535
|
+
# Initializing the facets in their Bit-representation.
|
|
536
|
+
self._bitrep_facets = facets_tuple_to_bit_rep_of_facets(facets, n_Vrepresentation)
|
|
537
|
+
|
|
538
|
+
# Initializing the Vrep as their Bit-representation.
|
|
539
|
+
self._bitrep_Vrep = facets_tuple_to_bit_rep_of_Vrep(facets, n_Vrepresentation)
|
|
540
|
+
|
|
541
|
+
self._initialize_far_face()
|
|
542
|
+
|
|
543
|
+
cdef _init_from_ListOfFaces(self, ListOfFaces facets, ListOfFaces Vrep):
|
|
544
|
+
"""
|
|
545
|
+
Initialize ``self`` from two ``ListOfFaces``.
|
|
546
|
+
"""
|
|
547
|
+
self._bitrep_facets = facets
|
|
548
|
+
self._bitrep_Vrep = Vrep
|
|
549
|
+
|
|
550
|
+
self._n_Hrepresentation = self._bitrep_facets.n_faces()
|
|
551
|
+
self._n_Vrepresentation = self._bitrep_Vrep.n_faces()
|
|
552
|
+
self._n_facets = self._n_Hrepresentation
|
|
553
|
+
|
|
554
|
+
self._initialize_far_face()
|
|
555
|
+
|
|
556
|
+
cdef _initialize_far_face(self):
|
|
557
|
+
"""
|
|
558
|
+
Initialize far_face if unbounded.
|
|
559
|
+
"""
|
|
560
|
+
if not self._bounded:
|
|
561
|
+
face_init(self._far_face, self.bitrep_facets().n_atoms(), self._n_facets)
|
|
562
|
+
Vrep_list_to_bit_rep(tuple(self._far_face_tuple), self._far_face)
|
|
563
|
+
|
|
564
|
+
cdef _init_as_trivial_polyhedron(self, int dimension):
|
|
565
|
+
"""
|
|
566
|
+
Initialize polyhedron equal to its affine hull.
|
|
567
|
+
"""
|
|
568
|
+
if dimension < -1:
|
|
569
|
+
raise ValueError("any polyhedron must have dimension at least -1")
|
|
570
|
+
self._dimension = dimension
|
|
571
|
+
|
|
572
|
+
if self._dimension == 0:
|
|
573
|
+
self._n_facets = 1
|
|
574
|
+
self._n_Vrepresentation = 1
|
|
575
|
+
else:
|
|
576
|
+
self._n_facets = 0
|
|
577
|
+
self._n_Vrepresentation = 0
|
|
578
|
+
|
|
579
|
+
# Initializing the facets in their Bit-representation.
|
|
580
|
+
self._bitrep_facets = facets_tuple_to_bit_rep_of_facets((), 0)
|
|
581
|
+
|
|
582
|
+
# Initializing the Vrep as their Bit-representation.
|
|
583
|
+
self._bitrep_Vrep = facets_tuple_to_bit_rep_of_Vrep((), 0)
|
|
584
|
+
|
|
585
|
+
def __dealloc__(self):
|
|
586
|
+
"""
|
|
587
|
+
TESTS::
|
|
588
|
+
|
|
589
|
+
sage: CombinatorialPolyhedron(-2) # indirect doctest
|
|
590
|
+
Traceback (most recent call last):
|
|
591
|
+
...
|
|
592
|
+
ValueError: any polyhedron must have dimension at least -1
|
|
593
|
+
"""
|
|
594
|
+
if not self._bounded:
|
|
595
|
+
face_free(self._far_face)
|
|
596
|
+
|
|
597
|
+
def _repr_(self):
|
|
598
|
+
r"""
|
|
599
|
+
Return a description of the combinatorial polyhedron.
|
|
600
|
+
|
|
601
|
+
EXAMPLES::
|
|
602
|
+
|
|
603
|
+
sage: P = polytopes.simplex()
|
|
604
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
605
|
+
sage: C._repr_()
|
|
606
|
+
'A 3-dimensional combinatorial polyhedron with 4 facets'
|
|
607
|
+
|
|
608
|
+
sage: P = Polyhedron(vertices=[])
|
|
609
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
610
|
+
sage: C._repr_()
|
|
611
|
+
'A -1-dimensional combinatorial polyhedron with 0 facets'
|
|
612
|
+
|
|
613
|
+
sage: P = Polyhedron(vertices=[[0,0]])
|
|
614
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
615
|
+
sage: C._repr_()
|
|
616
|
+
'A 0-dimensional combinatorial polyhedron with 0 facets'
|
|
617
|
+
|
|
618
|
+
sage: P = Polyhedron(lines=[[0,0,1],[0,1,0]])
|
|
619
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
620
|
+
sage: C._repr_()
|
|
621
|
+
'A 2-dimensional combinatorial polyhedron with 0 facets'
|
|
622
|
+
|
|
623
|
+
sage: P = Polyhedron(rays=[[1,0,0],[0,1,0],[-1,0,0]])
|
|
624
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
625
|
+
sage: C._repr_()
|
|
626
|
+
'A 2-dimensional combinatorial polyhedron with 1 facet'
|
|
627
|
+
"""
|
|
628
|
+
desc = "A {}-dimensional combinatorial polyhedron with {} facet"\
|
|
629
|
+
.format(self.dimension(), self.n_facets())
|
|
630
|
+
if self.n_facets() != 1:
|
|
631
|
+
desc += "s"
|
|
632
|
+
return desc
|
|
633
|
+
|
|
634
|
+
def __reduce__(self):
|
|
635
|
+
r"""
|
|
636
|
+
Override __reduce__ to correctly pickle/unpickle.
|
|
637
|
+
|
|
638
|
+
TESTS::
|
|
639
|
+
|
|
640
|
+
sage: # needs sage.combinat
|
|
641
|
+
sage: P = polytopes.permutahedron(4)
|
|
642
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
643
|
+
sage: C1 = loads(C.dumps())
|
|
644
|
+
sage: it = C.face_generator()
|
|
645
|
+
sage: it1 = C1.face_generator()
|
|
646
|
+
sage: tup = tuple((face.ambient_Vrepresentation(),
|
|
647
|
+
....: face.ambient_Hrepresentation()) for face in it)
|
|
648
|
+
sage: tup1 = tuple((face.ambient_Vrepresentation(),
|
|
649
|
+
....: face.ambient_Hrepresentation()) for face in it1)
|
|
650
|
+
sage: tup == tup1
|
|
651
|
+
True
|
|
652
|
+
|
|
653
|
+
sage: P = polytopes.cyclic_polytope(4,10)
|
|
654
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
655
|
+
sage: C1 = loads(C.dumps())
|
|
656
|
+
sage: it = C.face_generator()
|
|
657
|
+
sage: it1 = C1.face_generator()
|
|
658
|
+
sage: tup = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it)
|
|
659
|
+
sage: tup1 = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it1)
|
|
660
|
+
sage: tup == tup1
|
|
661
|
+
True
|
|
662
|
+
|
|
663
|
+
sage: P = Polyhedron(rays=[[1,0,0], [-1,0,0], [0,-1,0]])
|
|
664
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
665
|
+
sage: C1 = loads(C.dumps())
|
|
666
|
+
sage: it = C.face_generator()
|
|
667
|
+
sage: it1 = C1.face_generator()
|
|
668
|
+
sage: tup = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it)
|
|
669
|
+
sage: tup1 = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it1)
|
|
670
|
+
sage: tup == tup1
|
|
671
|
+
True
|
|
672
|
+
|
|
673
|
+
sage: P = Polyhedron(rays=[[1,0,0], [-1,0,0],
|
|
674
|
+
....: [0,-1,0], [0,1,0]])
|
|
675
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
676
|
+
sage: C1 = loads(C.dumps())
|
|
677
|
+
sage: it = C.face_generator()
|
|
678
|
+
sage: it1 = C1.face_generator()
|
|
679
|
+
sage: tup = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it)
|
|
680
|
+
sage: tup1 = tuple((face.ambient_Vrepresentation(), face.ambient_Hrepresentation()) for face in it1)
|
|
681
|
+
sage: tup == tup1
|
|
682
|
+
True
|
|
683
|
+
"""
|
|
684
|
+
# Give a constructor by list of facets.
|
|
685
|
+
if not self.is_bounded():
|
|
686
|
+
return (CombinatorialPolyhedron, (self.incidence_matrix(),
|
|
687
|
+
self.Vrepresentation(), self.Hrepresentation(),
|
|
688
|
+
True, self.far_face_tuple()))
|
|
689
|
+
else:
|
|
690
|
+
return (CombinatorialPolyhedron, (self.incidence_matrix(),
|
|
691
|
+
self.Vrepresentation(), self.Hrepresentation()))
|
|
692
|
+
|
|
693
|
+
def _test_bitsets(self, tester=None, **options):
|
|
694
|
+
"""
|
|
695
|
+
Test if the bitsets are consistent.
|
|
696
|
+
|
|
697
|
+
TESTS::
|
|
698
|
+
|
|
699
|
+
sage: P = polytopes.cube()
|
|
700
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
701
|
+
sage: C._test_bitsets()
|
|
702
|
+
"""
|
|
703
|
+
if tester is None:
|
|
704
|
+
tester = self._tester(**options)
|
|
705
|
+
|
|
706
|
+
cdef ListOfFaces facets = self.bitrep_facets()
|
|
707
|
+
cdef ListOfFaces Vrep = self.bitrep_Vrep()
|
|
708
|
+
|
|
709
|
+
tester.assertEqual(facets.matrix(), Vrep.matrix().transpose())
|
|
710
|
+
|
|
711
|
+
def Vrepresentation(self):
|
|
712
|
+
r"""
|
|
713
|
+
Return a list of names of ``[vertices, rays, lines]``.
|
|
714
|
+
|
|
715
|
+
EXAMPLES::
|
|
716
|
+
|
|
717
|
+
sage: P = Polyhedron(rays=[[1,0,0], [0,1,0], \
|
|
718
|
+
....: [0,0,1],[0,0,-1]])
|
|
719
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
720
|
+
sage: C.Vrepresentation()
|
|
721
|
+
(A line in the direction (0, 0, 1),
|
|
722
|
+
A ray in the direction (1, 0, 0),
|
|
723
|
+
A vertex at (0, 0, 0),
|
|
724
|
+
A ray in the direction (0, 1, 0))
|
|
725
|
+
|
|
726
|
+
sage: points = [(1,0,0), (0,1,0), (0,0,1),
|
|
727
|
+
....: (-1,0,0), (0,-1,0), (0,0,-1)]
|
|
728
|
+
sage: L = LatticePolytope(points)
|
|
729
|
+
sage: C = CombinatorialPolyhedron(L)
|
|
730
|
+
sage: C.Vrepresentation()
|
|
731
|
+
(M(1, 0, 0), M(0, 1, 0), M(0, 0, 1), M(-1, 0, 0), M(0, -1, 0), M(0, 0, -1))
|
|
732
|
+
|
|
733
|
+
sage: M = Cone([(1,0), (0,1)])
|
|
734
|
+
sage: CombinatorialPolyhedron(M).Vrepresentation()
|
|
735
|
+
(N(1, 0), N(0, 1), N(0, 0))
|
|
736
|
+
"""
|
|
737
|
+
if self.Vrep() is not None:
|
|
738
|
+
return self.Vrep()
|
|
739
|
+
else:
|
|
740
|
+
return tuple(smallInteger(i) for i in range(self.n_Vrepresentation()))
|
|
741
|
+
|
|
742
|
+
def Hrepresentation(self):
|
|
743
|
+
r"""
|
|
744
|
+
Return a list of names of facets and possibly some equations.
|
|
745
|
+
|
|
746
|
+
EXAMPLES::
|
|
747
|
+
|
|
748
|
+
sage: P = polytopes.permutahedron(3)
|
|
749
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
750
|
+
sage: C.Hrepresentation()
|
|
751
|
+
(An inequality (1, 1, 0) x - 3 >= 0,
|
|
752
|
+
An inequality (-1, -1, 0) x + 5 >= 0,
|
|
753
|
+
An inequality (0, 1, 0) x - 1 >= 0,
|
|
754
|
+
An inequality (-1, 0, 0) x + 3 >= 0,
|
|
755
|
+
An inequality (1, 0, 0) x - 1 >= 0,
|
|
756
|
+
An inequality (0, -1, 0) x + 3 >= 0,
|
|
757
|
+
An equation (1, 1, 1) x - 6 == 0)
|
|
758
|
+
|
|
759
|
+
sage: points = [(1,0,0), (0,1,0), (0,0,1),
|
|
760
|
+
....: (-1,0,0), (0,-1,0), (0,0,-1)]
|
|
761
|
+
sage: L = LatticePolytope(points)
|
|
762
|
+
sage: C = CombinatorialPolyhedron(L)
|
|
763
|
+
sage: C.Hrepresentation()
|
|
764
|
+
(N(1, -1, -1),
|
|
765
|
+
N(1, 1, -1),
|
|
766
|
+
N(1, 1, 1),
|
|
767
|
+
N(1, -1, 1),
|
|
768
|
+
N(-1, -1, 1),
|
|
769
|
+
N(-1, -1, -1),
|
|
770
|
+
N(-1, 1, -1),
|
|
771
|
+
N(-1, 1, 1))
|
|
772
|
+
|
|
773
|
+
sage: M = Cone([(1,0), (0,1)])
|
|
774
|
+
sage: CombinatorialPolyhedron(M).Hrepresentation()
|
|
775
|
+
(M(0, 1), M(1, 0))
|
|
776
|
+
"""
|
|
777
|
+
if self.facet_names() is not None:
|
|
778
|
+
return self.facet_names() + self.equations()
|
|
779
|
+
else:
|
|
780
|
+
return tuple(smallInteger(i) for i in range(self.n_Hrepresentation()))
|
|
781
|
+
|
|
782
|
+
def dimension(self):
|
|
783
|
+
r"""
|
|
784
|
+
Return the dimension of the polyhedron.
|
|
785
|
+
|
|
786
|
+
EXAMPLES::
|
|
787
|
+
|
|
788
|
+
sage: C = CombinatorialPolyhedron([(1,2,3), (1,2,4),
|
|
789
|
+
....: (1,3,4), (2,3,4)])
|
|
790
|
+
sage: C.dimension()
|
|
791
|
+
3
|
|
792
|
+
|
|
793
|
+
sage: P = Polyhedron(rays=[[1,0,0],[0,1,0],[0,0,1],[0,0,-1]])
|
|
794
|
+
sage: CombinatorialPolyhedron(P).dimension()
|
|
795
|
+
3
|
|
796
|
+
|
|
797
|
+
``dim`` is an alias::
|
|
798
|
+
|
|
799
|
+
sage: CombinatorialPolyhedron(P).dim()
|
|
800
|
+
3
|
|
801
|
+
"""
|
|
802
|
+
if self._dimension == -2:
|
|
803
|
+
# Dimension not computed yet.
|
|
804
|
+
if self.n_facets() == -1:
|
|
805
|
+
raise ValueError("the combinatorial polyhedron was not initialized")
|
|
806
|
+
elif self.n_facets() == 0:
|
|
807
|
+
# The dimension of a trivial polyhedron is assumed to contain
|
|
808
|
+
# exactly one "vertex" and for each dimension one "line" as in
|
|
809
|
+
# :class:`~sage.geometry.polyhedron.parent.Polyhedron_base`
|
|
810
|
+
self._dimension = self.n_Vrepresentation() - 1
|
|
811
|
+
elif not self.is_bounded() or self.n_facets() <= self.n_Vrepresentation():
|
|
812
|
+
self._dimension = self.bitrep_facets().compute_dimension()
|
|
813
|
+
else:
|
|
814
|
+
# If the polyhedron has many facets,
|
|
815
|
+
# calculating the dimension of the dual will be faster.
|
|
816
|
+
# The dual exists, if the polyhedron is bounded.
|
|
817
|
+
self._dimension = self.bitrep_facets().compute_dimension()
|
|
818
|
+
return smallInteger(self._dimension)
|
|
819
|
+
|
|
820
|
+
dim = dimension
|
|
821
|
+
|
|
822
|
+
@cached_method
|
|
823
|
+
def n_vertices(self):
|
|
824
|
+
r"""
|
|
825
|
+
Return the number of vertices.
|
|
826
|
+
|
|
827
|
+
Is equivalent to ``len(self.vertices())``.
|
|
828
|
+
|
|
829
|
+
EXAMPLES::
|
|
830
|
+
|
|
831
|
+
sage: P = polytopes.cube()
|
|
832
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
833
|
+
sage: C.n_vertices()
|
|
834
|
+
8
|
|
835
|
+
|
|
836
|
+
sage: P = polytopes.cyclic_polytope(4,20)
|
|
837
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
838
|
+
sage: C.n_vertices()
|
|
839
|
+
20
|
|
840
|
+
|
|
841
|
+
sage: P = Polyhedron(lines=[[0,1]], vertices=[[1,0], [-1,0]])
|
|
842
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
843
|
+
sage: C.n_vertices()
|
|
844
|
+
0
|
|
845
|
+
|
|
846
|
+
sage: P = Polyhedron(rays=[[1,0,0], [0,1,0]], lines=[[0,0,1]])
|
|
847
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
848
|
+
sage: C.n_vertices()
|
|
849
|
+
0
|
|
850
|
+
|
|
851
|
+
sage: C = CombinatorialPolyhedron(4)
|
|
852
|
+
sage: C.f_vector()
|
|
853
|
+
(1, 0, 0, 0, 0, 1)
|
|
854
|
+
sage: C.n_vertices()
|
|
855
|
+
0
|
|
856
|
+
|
|
857
|
+
sage: C = CombinatorialPolyhedron(0)
|
|
858
|
+
sage: C.f_vector()
|
|
859
|
+
(1, 1)
|
|
860
|
+
sage: C.n_vertices()
|
|
861
|
+
1
|
|
862
|
+
"""
|
|
863
|
+
if self.dimension() == 0:
|
|
864
|
+
# This specific trivial polyhedron needs special attention.
|
|
865
|
+
return smallInteger(1)
|
|
866
|
+
if not self.is_bounded():
|
|
867
|
+
# Some elements in the ``Vrep`` might not correspond to actual combinatorial vertices.
|
|
868
|
+
return len(self.vertices())
|
|
869
|
+
else:
|
|
870
|
+
return smallInteger(self.n_Vrepresentation())
|
|
871
|
+
|
|
872
|
+
def vertices(self, names=True):
|
|
873
|
+
r"""
|
|
874
|
+
Return the elements in the Vrepresentation that are vertices.
|
|
875
|
+
|
|
876
|
+
In case of an unbounded polyhedron, there might be lines and
|
|
877
|
+
rays in the Vrepresentation.
|
|
878
|
+
|
|
879
|
+
If ``names`` is set to ``False``, then the vertices are given by
|
|
880
|
+
their indices in the Vrepresentation.
|
|
881
|
+
|
|
882
|
+
EXAMPLES::
|
|
883
|
+
|
|
884
|
+
sage: P = Polyhedron(rays=[[1,0,0],[0,1,0],[0,0,1]])
|
|
885
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
886
|
+
sage: C.vertices()
|
|
887
|
+
(A vertex at (0, 0, 0),)
|
|
888
|
+
sage: C.Vrepresentation()
|
|
889
|
+
(A vertex at (0, 0, 0),
|
|
890
|
+
A ray in the direction (0, 0, 1),
|
|
891
|
+
A ray in the direction (0, 1, 0),
|
|
892
|
+
A ray in the direction (1, 0, 0))
|
|
893
|
+
sage: P = polytopes.cross_polytope(3)
|
|
894
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
895
|
+
sage: C.vertices()
|
|
896
|
+
(A vertex at (-1, 0, 0),
|
|
897
|
+
A vertex at (0, -1, 0),
|
|
898
|
+
A vertex at (0, 0, -1),
|
|
899
|
+
A vertex at (0, 0, 1),
|
|
900
|
+
A vertex at (0, 1, 0),
|
|
901
|
+
A vertex at (1, 0, 0))
|
|
902
|
+
sage: C.vertices(names=False)
|
|
903
|
+
(0, 1, 2, 3, 4, 5)
|
|
904
|
+
|
|
905
|
+
sage: points = [(1,0,0), (0,1,0), (0,0,1),
|
|
906
|
+
....: (-1,0,0), (0,-1,0), (0,0,-1)]
|
|
907
|
+
sage: L = LatticePolytope(points)
|
|
908
|
+
sage: C = CombinatorialPolyhedron(L)
|
|
909
|
+
sage: C.vertices()
|
|
910
|
+
(M(1, 0, 0), M(0, 1, 0), M(0, 0, 1), M(-1, 0, 0), M(0, -1, 0), M(0, 0, -1))
|
|
911
|
+
sage: C.vertices(names=False)
|
|
912
|
+
(0, 1, 2, 3, 4, 5)
|
|
913
|
+
|
|
914
|
+
sage: P = Polyhedron(vertices=[[0,0]])
|
|
915
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
916
|
+
sage: C.vertices()
|
|
917
|
+
(A vertex at (0, 0),)
|
|
918
|
+
"""
|
|
919
|
+
if unlikely(self.dimension() == 0):
|
|
920
|
+
# Handling the case of a trivial polyhedron of dimension `0`.
|
|
921
|
+
if names and self.Vrep():
|
|
922
|
+
return (self.Vrep()[0],)
|
|
923
|
+
else:
|
|
924
|
+
return (smallInteger(0),)
|
|
925
|
+
if not self.is_bounded():
|
|
926
|
+
it = self.face_iter(0)
|
|
927
|
+
try:
|
|
928
|
+
# The Polyhedron has at least one vertex.
|
|
929
|
+
# In this case every element in the ``Vrep``
|
|
930
|
+
# that is not contained in the far face
|
|
931
|
+
# is a vertex.
|
|
932
|
+
next(it)
|
|
933
|
+
except StopIteration:
|
|
934
|
+
# The Polyhedron has no vertex.
|
|
935
|
+
return ()
|
|
936
|
+
if names and self.Vrep():
|
|
937
|
+
return tuple(self.Vrep()[i] for i in range(self.n_Vrepresentation()) if i not in self.far_face_tuple())
|
|
938
|
+
else:
|
|
939
|
+
return tuple(smallInteger(i) for i in range(self.n_Vrepresentation()) if i not in self.far_face_tuple())
|
|
940
|
+
|
|
941
|
+
def n_facets(self):
|
|
942
|
+
r"""
|
|
943
|
+
Return the number of facets.
|
|
944
|
+
|
|
945
|
+
Is equivalent to ``len(self.facets())``.
|
|
946
|
+
|
|
947
|
+
EXAMPLES::
|
|
948
|
+
|
|
949
|
+
sage: P = polytopes.cube()
|
|
950
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
951
|
+
sage: C.n_facets()
|
|
952
|
+
6
|
|
953
|
+
|
|
954
|
+
sage: P = polytopes.cyclic_polytope(4,20)
|
|
955
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
956
|
+
sage: C.n_facets()
|
|
957
|
+
170
|
|
958
|
+
|
|
959
|
+
sage: P = Polyhedron(lines=[[0,1]], vertices=[[1,0], [-1,0]])
|
|
960
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
961
|
+
sage: C.n_facets()
|
|
962
|
+
2
|
|
963
|
+
|
|
964
|
+
sage: P = Polyhedron(rays=[[1,0], [-1,0], [0,1]])
|
|
965
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
966
|
+
sage: C.n_facets()
|
|
967
|
+
1
|
|
968
|
+
|
|
969
|
+
sage: C = CombinatorialPolyhedron(-1)
|
|
970
|
+
sage: C.f_vector()
|
|
971
|
+
(1)
|
|
972
|
+
sage: C.n_facets()
|
|
973
|
+
0
|
|
974
|
+
|
|
975
|
+
Facets are defined to be the maximal nontrivial faces.
|
|
976
|
+
The ``0``-dimensional polyhedron does not have nontrivial faces::
|
|
977
|
+
|
|
978
|
+
sage: C = CombinatorialPolyhedron(0)
|
|
979
|
+
sage: C.f_vector()
|
|
980
|
+
(1, 1)
|
|
981
|
+
sage: C.n_facets()
|
|
982
|
+
0
|
|
983
|
+
"""
|
|
984
|
+
if unlikely(self._dimension == 0):
|
|
985
|
+
# This trivial polyhedron needs special attention.
|
|
986
|
+
return smallInteger(0)
|
|
987
|
+
return smallInteger(self._n_facets)
|
|
988
|
+
|
|
989
|
+
def facets(self, names=True):
|
|
990
|
+
r"""
|
|
991
|
+
Return the facets as lists of ``[vertices, rays, lines]``.
|
|
992
|
+
|
|
993
|
+
If ``names`` is ``False``, then the Vrepresentatives in the facets
|
|
994
|
+
are given by their indices in the Vrepresentation.
|
|
995
|
+
|
|
996
|
+
The facets are the maximal nontrivial faces.
|
|
997
|
+
|
|
998
|
+
EXAMPLES::
|
|
999
|
+
|
|
1000
|
+
sage: P = polytopes.cube()
|
|
1001
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1002
|
+
sage: C.facets()
|
|
1003
|
+
((A vertex at (1, -1, -1),
|
|
1004
|
+
A vertex at (1, 1, -1),
|
|
1005
|
+
A vertex at (1, 1, 1),
|
|
1006
|
+
A vertex at (1, -1, 1)),
|
|
1007
|
+
(A vertex at (1, 1, -1),
|
|
1008
|
+
A vertex at (1, 1, 1),
|
|
1009
|
+
A vertex at (-1, 1, -1),
|
|
1010
|
+
A vertex at (-1, 1, 1)),
|
|
1011
|
+
(A vertex at (1, 1, 1),
|
|
1012
|
+
A vertex at (1, -1, 1),
|
|
1013
|
+
A vertex at (-1, -1, 1),
|
|
1014
|
+
A vertex at (-1, 1, 1)),
|
|
1015
|
+
(A vertex at (-1, -1, 1),
|
|
1016
|
+
A vertex at (-1, -1, -1),
|
|
1017
|
+
A vertex at (-1, 1, -1),
|
|
1018
|
+
A vertex at (-1, 1, 1)),
|
|
1019
|
+
(A vertex at (1, -1, -1),
|
|
1020
|
+
A vertex at (1, 1, -1),
|
|
1021
|
+
A vertex at (-1, -1, -1),
|
|
1022
|
+
A vertex at (-1, 1, -1)),
|
|
1023
|
+
(A vertex at (1, -1, -1),
|
|
1024
|
+
A vertex at (1, -1, 1),
|
|
1025
|
+
A vertex at (-1, -1, 1),
|
|
1026
|
+
A vertex at (-1, -1, -1)))
|
|
1027
|
+
sage: C.facets(names=False)
|
|
1028
|
+
((0, 1, 2, 3),
|
|
1029
|
+
(1, 2, 6, 7),
|
|
1030
|
+
(2, 3, 4, 7),
|
|
1031
|
+
(4, 5, 6, 7),
|
|
1032
|
+
(0, 1, 5, 6),
|
|
1033
|
+
(0, 3, 4, 5))
|
|
1034
|
+
|
|
1035
|
+
The empty face is trivial and hence the ``0``-dimensional
|
|
1036
|
+
polyhedron does not have facets::
|
|
1037
|
+
|
|
1038
|
+
sage: C = CombinatorialPolyhedron(0)
|
|
1039
|
+
sage: C.facets()
|
|
1040
|
+
()
|
|
1041
|
+
"""
|
|
1042
|
+
if unlikely(self.dimension() <= 0):
|
|
1043
|
+
# Special attention for this trivial case.
|
|
1044
|
+
# Facets are defined to be nontrivial faces of codimension 1.
|
|
1045
|
+
# The empty face is trivial.
|
|
1046
|
+
return ()
|
|
1047
|
+
|
|
1048
|
+
# It is essential to have the facets in the exact same order as
|
|
1049
|
+
# on input, so that pickle/unpickle by :meth:`reduce` works.
|
|
1050
|
+
# Every facet knows its index by the facet representation.
|
|
1051
|
+
face_iter = self.face_iter(self.dimension() - 1, algorithm='primal')
|
|
1052
|
+
facets = [None] * self.n_facets()
|
|
1053
|
+
for face in face_iter:
|
|
1054
|
+
index = face.ambient_H_indices()[0]
|
|
1055
|
+
if names:
|
|
1056
|
+
verts = face.ambient_Vrepresentation()
|
|
1057
|
+
else:
|
|
1058
|
+
verts = face.ambient_V_indices()
|
|
1059
|
+
facets[index] = verts
|
|
1060
|
+
|
|
1061
|
+
return tuple(facets)
|
|
1062
|
+
|
|
1063
|
+
@cached_method
|
|
1064
|
+
def incidence_matrix(self):
|
|
1065
|
+
"""
|
|
1066
|
+
Return the incidence matrix.
|
|
1067
|
+
|
|
1068
|
+
.. NOTE::
|
|
1069
|
+
|
|
1070
|
+
The columns correspond to inequalities/equations in the
|
|
1071
|
+
order :meth:`Hrepresentation`, the rows correspond to
|
|
1072
|
+
vertices/rays/lines in the order
|
|
1073
|
+
:meth:`Vrepresentation`.
|
|
1074
|
+
|
|
1075
|
+
.. SEEALSO::
|
|
1076
|
+
|
|
1077
|
+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.incidence_matrix`.
|
|
1078
|
+
|
|
1079
|
+
EXAMPLES::
|
|
1080
|
+
|
|
1081
|
+
sage: P = polytopes.cube()
|
|
1082
|
+
sage: C = P.combinatorial_polyhedron()
|
|
1083
|
+
sage: C.incidence_matrix()
|
|
1084
|
+
[1 0 0 0 1 1]
|
|
1085
|
+
[1 1 0 0 1 0]
|
|
1086
|
+
[1 1 1 0 0 0]
|
|
1087
|
+
[1 0 1 0 0 1]
|
|
1088
|
+
[0 0 1 1 0 1]
|
|
1089
|
+
[0 0 0 1 1 1]
|
|
1090
|
+
[0 1 0 1 1 0]
|
|
1091
|
+
[0 1 1 1 0 0]
|
|
1092
|
+
|
|
1093
|
+
In this case the incidence matrix is only computed once::
|
|
1094
|
+
|
|
1095
|
+
sage: P.incidence_matrix() is C.incidence_matrix()
|
|
1096
|
+
True
|
|
1097
|
+
sage: C.incidence_matrix.clear_cache()
|
|
1098
|
+
sage: C.incidence_matrix() is P.incidence_matrix()
|
|
1099
|
+
False
|
|
1100
|
+
sage: C.incidence_matrix() == P.incidence_matrix()
|
|
1101
|
+
True
|
|
1102
|
+
|
|
1103
|
+
::
|
|
1104
|
+
|
|
1105
|
+
sage: # needs sage.combinat
|
|
1106
|
+
sage: P = polytopes.permutahedron(5, backend='field')
|
|
1107
|
+
sage: C = P.combinatorial_polyhedron()
|
|
1108
|
+
sage: C.incidence_matrix.clear_cache()
|
|
1109
|
+
sage: C.incidence_matrix() == P.incidence_matrix()
|
|
1110
|
+
True
|
|
1111
|
+
|
|
1112
|
+
The incidence matrix is consistent with
|
|
1113
|
+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.incidence_matrix`::
|
|
1114
|
+
|
|
1115
|
+
sage: P = Polyhedron([[0,0]])
|
|
1116
|
+
sage: P.incidence_matrix()
|
|
1117
|
+
[1 1]
|
|
1118
|
+
sage: C = P.combinatorial_polyhedron()
|
|
1119
|
+
sage: C.incidence_matrix.clear_cache()
|
|
1120
|
+
sage: P.combinatorial_polyhedron().incidence_matrix()
|
|
1121
|
+
[1 1]
|
|
1122
|
+
|
|
1123
|
+
TESTS:
|
|
1124
|
+
|
|
1125
|
+
Check that :issue:`29455` is fixed::
|
|
1126
|
+
|
|
1127
|
+
sage: C = Polyhedron([[0]]).combinatorial_polyhedron()
|
|
1128
|
+
sage: C.incidence_matrix.clear_cache()
|
|
1129
|
+
sage: C.incidence_matrix()
|
|
1130
|
+
[1]
|
|
1131
|
+
sage: C = CombinatorialPolyhedron(-1)
|
|
1132
|
+
sage: C.incidence_matrix.clear_cache()
|
|
1133
|
+
sage: C.incidence_matrix()
|
|
1134
|
+
[]
|
|
1135
|
+
|
|
1136
|
+
Check that the base ring is ``ZZ``, see :issue:`29840`::
|
|
1137
|
+
|
|
1138
|
+
sage: C = CombinatorialPolyhedron([[0,1,2], [0,1,3], [0,2,3], [1,2,3]])
|
|
1139
|
+
sage: C.incidence_matrix().base_ring()
|
|
1140
|
+
Integer Ring
|
|
1141
|
+
"""
|
|
1142
|
+
from sage.rings.integer_ring import ZZ
|
|
1143
|
+
from sage.matrix.constructor import matrix
|
|
1144
|
+
incidence_matrix = matrix(
|
|
1145
|
+
ZZ, self.n_Vrepresentation(), self.n_Hrepresentation(), 0)
|
|
1146
|
+
|
|
1147
|
+
if self.dim() < 1:
|
|
1148
|
+
# Small cases.
|
|
1149
|
+
if self.dim() == 0:
|
|
1150
|
+
try:
|
|
1151
|
+
# To be consistent with ``Polyhedron_base``,
|
|
1152
|
+
for i in range(self.n_Hrepresentation()):
|
|
1153
|
+
incidence_matrix.set_unsafe_int(0, i, 1)
|
|
1154
|
+
except AttributeError:
|
|
1155
|
+
for i in range(self.n_Hrepresentation()):
|
|
1156
|
+
incidence_matrix[0, i] = 1
|
|
1157
|
+
incidence_matrix.set_immutable()
|
|
1158
|
+
return incidence_matrix
|
|
1159
|
+
|
|
1160
|
+
# If equations are present, we add them as last columns.
|
|
1161
|
+
n_facets = self.n_facets()
|
|
1162
|
+
if self.facet_names() is not None:
|
|
1163
|
+
n_equations = len(self.equations())
|
|
1164
|
+
try:
|
|
1165
|
+
for Hindex in range(n_facets, n_facets + n_equations):
|
|
1166
|
+
for Vindex in range(self.n_Vrepresentation()):
|
|
1167
|
+
incidence_matrix.set_unsafe_int(Vindex, Hindex, 1)
|
|
1168
|
+
except AttributeError:
|
|
1169
|
+
for Hindex in range(n_facets, n_facets + n_equations):
|
|
1170
|
+
for Vindex in range(self.n_Vrepresentation()):
|
|
1171
|
+
incidence_matrix[Vindex, Hindex] = 1
|
|
1172
|
+
|
|
1173
|
+
facet_iter = self.face_iter(self.dimension() - 1, algorithm='primal')
|
|
1174
|
+
for facet in facet_iter:
|
|
1175
|
+
Hindex = facet.ambient_H_indices()[0]
|
|
1176
|
+
try:
|
|
1177
|
+
for Vindex in facet.ambient_V_indices():
|
|
1178
|
+
incidence_matrix.set_unsafe_int(Vindex, Hindex, 1)
|
|
1179
|
+
except AttributeError:
|
|
1180
|
+
for Vindex in facet.ambient_V_indices():
|
|
1181
|
+
incidence_matrix[Vindex, Hindex] = 1
|
|
1182
|
+
|
|
1183
|
+
incidence_matrix.set_immutable()
|
|
1184
|
+
|
|
1185
|
+
return incidence_matrix
|
|
1186
|
+
|
|
1187
|
+
cdef int _algorithm_to_dual(self, algorithm) except -2:
|
|
1188
|
+
if algorithm == 'primal':
|
|
1189
|
+
return 0
|
|
1190
|
+
elif algorithm == 'dual':
|
|
1191
|
+
if not self.is_bounded():
|
|
1192
|
+
raise ValueError("dual algorithm only available for bounded polyhedra")
|
|
1193
|
+
return 1
|
|
1194
|
+
elif algorithm is None:
|
|
1195
|
+
return -1
|
|
1196
|
+
else:
|
|
1197
|
+
raise ValueError("algorithm must be 'primal', 'dual' or None")
|
|
1198
|
+
|
|
1199
|
+
def edges(self, names=True, algorithm=None):
|
|
1200
|
+
r"""
|
|
1201
|
+
Return the edges of the polyhedron, i.e. the rank 1 faces.
|
|
1202
|
+
|
|
1203
|
+
INPUT:
|
|
1204
|
+
|
|
1205
|
+
- ``names`` -- boolean (default: ``True``); if ``False``,
|
|
1206
|
+
then the Vrepresentatives in the edges are given by
|
|
1207
|
+
their indices in the Vrepresentation
|
|
1208
|
+
|
|
1209
|
+
- ``algorithm`` -- string (optional);
|
|
1210
|
+
specify whether the face generator starts with facets or vertices:
|
|
1211
|
+
* ``'primal'`` -- start with the facets
|
|
1212
|
+
* ``'dual'`` -- start with the vertices
|
|
1213
|
+
* ``None`` -- choose automatically
|
|
1214
|
+
|
|
1215
|
+
.. NOTE::
|
|
1216
|
+
|
|
1217
|
+
To compute edges and f_vector, first compute the edges.
|
|
1218
|
+
This might be faster.
|
|
1219
|
+
|
|
1220
|
+
EXAMPLES::
|
|
1221
|
+
|
|
1222
|
+
sage: P = polytopes.cyclic_polytope(3,5)
|
|
1223
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1224
|
+
sage: C.edges()
|
|
1225
|
+
((A vertex at (3, 9, 27), A vertex at (4, 16, 64)),
|
|
1226
|
+
(A vertex at (2, 4, 8), A vertex at (4, 16, 64)),
|
|
1227
|
+
(A vertex at (1, 1, 1), A vertex at (4, 16, 64)),
|
|
1228
|
+
(A vertex at (0, 0, 0), A vertex at (4, 16, 64)),
|
|
1229
|
+
(A vertex at (2, 4, 8), A vertex at (3, 9, 27)),
|
|
1230
|
+
(A vertex at (0, 0, 0), A vertex at (3, 9, 27)),
|
|
1231
|
+
(A vertex at (1, 1, 1), A vertex at (2, 4, 8)),
|
|
1232
|
+
(A vertex at (0, 0, 0), A vertex at (2, 4, 8)),
|
|
1233
|
+
(A vertex at (0, 0, 0), A vertex at (1, 1, 1)))
|
|
1234
|
+
|
|
1235
|
+
sage: C.edges(names=False)
|
|
1236
|
+
((3, 4), (2, 4), (1, 4), (0, 4), (2, 3), (0, 3), (1, 2), (0, 2), (0, 1))
|
|
1237
|
+
|
|
1238
|
+
sage: P = Polyhedron(rays=[[-1,0],[1,0]])
|
|
1239
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1240
|
+
sage: C.edges()
|
|
1241
|
+
((A line in the direction (1, 0), A vertex at (0, 0)),)
|
|
1242
|
+
|
|
1243
|
+
sage: P = Polyhedron(vertices=[[0,0],[1,0]])
|
|
1244
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1245
|
+
sage: C.edges()
|
|
1246
|
+
((A vertex at (0, 0), A vertex at (1, 0)),)
|
|
1247
|
+
|
|
1248
|
+
sage: from itertools import combinations
|
|
1249
|
+
sage: N = combinations(['a','b','c','d','e'], 4)
|
|
1250
|
+
sage: C = CombinatorialPolyhedron(N)
|
|
1251
|
+
sage: C.edges()
|
|
1252
|
+
(('d', 'e'),
|
|
1253
|
+
('c', 'e'),
|
|
1254
|
+
('b', 'e'),
|
|
1255
|
+
('a', 'e'),
|
|
1256
|
+
('c', 'd'),
|
|
1257
|
+
('b', 'd'),
|
|
1258
|
+
('a', 'd'),
|
|
1259
|
+
('b', 'c'),
|
|
1260
|
+
('a', 'c'),
|
|
1261
|
+
('a', 'b'))
|
|
1262
|
+
"""
|
|
1263
|
+
self._compute_edges(self._algorithm_to_dual(algorithm))
|
|
1264
|
+
|
|
1265
|
+
# Mapping the indices of the Vrep to the names, if requested.
|
|
1266
|
+
if self.Vrep() is not None and names is True:
|
|
1267
|
+
def f(size_t i):
|
|
1268
|
+
return self.Vrep()[i]
|
|
1269
|
+
else:
|
|
1270
|
+
def f(size_t i):
|
|
1271
|
+
return smallInteger(i)
|
|
1272
|
+
|
|
1273
|
+
cdef size_t j
|
|
1274
|
+
return tuple((f(self._edges.get(j).first),
|
|
1275
|
+
f(self._edges.get(j).second))
|
|
1276
|
+
for j in range(self._edges.length))
|
|
1277
|
+
|
|
1278
|
+
def vertex_graph(self, names=True, algorithm=None):
|
|
1279
|
+
r"""
|
|
1280
|
+
Return a graph in which the vertices correspond to vertices
|
|
1281
|
+
of the polyhedron, and edges to bounded rank 1 faces.
|
|
1282
|
+
|
|
1283
|
+
INPUT:
|
|
1284
|
+
|
|
1285
|
+
- ``names`` -- boolean (default: ``True``); if ``False``,
|
|
1286
|
+
then the nodes of the graph are labeld by the
|
|
1287
|
+
indices of the Vrepresentation
|
|
1288
|
+
|
|
1289
|
+
- ``algorithm`` -- string (optional);
|
|
1290
|
+
specify whether the face generator starts with facets or vertices:
|
|
1291
|
+
* ``'primal'`` -- start with the facets
|
|
1292
|
+
* ``'dual'`` -- start with the vertices
|
|
1293
|
+
* ``None`` -- choose automatically
|
|
1294
|
+
|
|
1295
|
+
EXAMPLES::
|
|
1296
|
+
|
|
1297
|
+
sage: P = polytopes.cyclic_polytope(3,5)
|
|
1298
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1299
|
+
sage: G = C.vertex_graph(); G # needs sage.graphs
|
|
1300
|
+
Graph on 5 vertices
|
|
1301
|
+
sage: sorted(G.degree()) # needs sage.graphs
|
|
1302
|
+
[3, 3, 4, 4, 4]
|
|
1303
|
+
|
|
1304
|
+
sage: P = Polyhedron(rays=[[1]])
|
|
1305
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1306
|
+
sage: C.graph() # needs sage.graphs
|
|
1307
|
+
Graph on 1 vertex
|
|
1308
|
+
"""
|
|
1309
|
+
vertices = self.vertices(names=names)
|
|
1310
|
+
|
|
1311
|
+
# Getting the bounded edges.
|
|
1312
|
+
edges = tuple(edge for edge in self.edges(names=names, algorithm=algorithm)
|
|
1313
|
+
if edge[0] in vertices and edge[1] in vertices)
|
|
1314
|
+
|
|
1315
|
+
from sage.graphs.graph import Graph
|
|
1316
|
+
return Graph([vertices, edges], format='vertices_and_edges')
|
|
1317
|
+
|
|
1318
|
+
graph = vertex_graph
|
|
1319
|
+
|
|
1320
|
+
@cached_method
|
|
1321
|
+
def vertex_adjacency_matrix(self, algorithm=None):
|
|
1322
|
+
"""
|
|
1323
|
+
Return the binary matrix of vertex adjacencies.
|
|
1324
|
+
|
|
1325
|
+
INPUT:
|
|
1326
|
+
|
|
1327
|
+
- ``algorithm`` -- string (optional);
|
|
1328
|
+
specify whether the face generator starts with facets or vertices:
|
|
1329
|
+
* ``'primal'`` -- start with the facets
|
|
1330
|
+
* ``'dual'`` -- start with the vertices
|
|
1331
|
+
* ``None`` -- choose automatically
|
|
1332
|
+
|
|
1333
|
+
.. SEEALSO::
|
|
1334
|
+
|
|
1335
|
+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.vertex_adjacency_matrix`.
|
|
1336
|
+
|
|
1337
|
+
EXAMPLES::
|
|
1338
|
+
|
|
1339
|
+
sage: P = polytopes.cube()
|
|
1340
|
+
sage: C = P.combinatorial_polyhedron()
|
|
1341
|
+
sage: C.vertex_adjacency_matrix()
|
|
1342
|
+
[0 1 0 1 0 1 0 0]
|
|
1343
|
+
[1 0 1 0 0 0 1 0]
|
|
1344
|
+
[0 1 0 1 0 0 0 1]
|
|
1345
|
+
[1 0 1 0 1 0 0 0]
|
|
1346
|
+
[0 0 0 1 0 1 0 1]
|
|
1347
|
+
[1 0 0 0 1 0 1 0]
|
|
1348
|
+
[0 1 0 0 0 1 0 1]
|
|
1349
|
+
[0 0 1 0 1 0 1 0]
|
|
1350
|
+
|
|
1351
|
+
TESTS::
|
|
1352
|
+
|
|
1353
|
+
sage: CombinatorialPolyhedron(-1).vertex_adjacency_matrix()
|
|
1354
|
+
[]
|
|
1355
|
+
sage: CombinatorialPolyhedron(0).vertex_adjacency_matrix()
|
|
1356
|
+
[0]
|
|
1357
|
+
sage: polytopes.cube().vertex_adjacency_matrix().is_immutable()
|
|
1358
|
+
True
|
|
1359
|
+
"""
|
|
1360
|
+
from sage.rings.integer_ring import ZZ
|
|
1361
|
+
from sage.matrix.constructor import matrix
|
|
1362
|
+
cdef Matrix_dense adjacency_matrix = matrix(
|
|
1363
|
+
ZZ, self.n_Vrepresentation(), self.n_Vrepresentation(), 0)
|
|
1364
|
+
cdef size_t i, first, second
|
|
1365
|
+
|
|
1366
|
+
self._compute_edges(self._algorithm_to_dual(algorithm))
|
|
1367
|
+
try:
|
|
1368
|
+
for i in range(self._edges.length):
|
|
1369
|
+
first = self._edges.get(i).first
|
|
1370
|
+
second = self._edges.get(i).second
|
|
1371
|
+
adjacency_matrix.set_unsafe_int(first, second, 1)
|
|
1372
|
+
adjacency_matrix.set_unsafe_int(second, first, 1)
|
|
1373
|
+
except AttributeError:
|
|
1374
|
+
first = self._edges.get(i).first
|
|
1375
|
+
second = self._edges.get(i).second
|
|
1376
|
+
adjacency_matrix[first, second] = adjacency_matrix[second, first] = 1
|
|
1377
|
+
adjacency_matrix.set_immutable()
|
|
1378
|
+
return adjacency_matrix
|
|
1379
|
+
|
|
1380
|
+
def ridges(self, add_equations=False, names=True, algorithm=None):
|
|
1381
|
+
r"""
|
|
1382
|
+
Return the ridges.
|
|
1383
|
+
|
|
1384
|
+
The ridges of a polyhedron are the faces
|
|
1385
|
+
contained in exactly two facets.
|
|
1386
|
+
|
|
1387
|
+
To obtain all faces of codimension 1 use
|
|
1388
|
+
:meth:`CombinatorialPolyhedron.face_generator` instead.
|
|
1389
|
+
|
|
1390
|
+
The ridges will be given by the facets, they are contained in.
|
|
1391
|
+
|
|
1392
|
+
INPUT:
|
|
1393
|
+
|
|
1394
|
+
- ``add_equations`` -- if ``True``, then equations of the polyhedron
|
|
1395
|
+
will be added (only applicable when ``names`` is ``True``)
|
|
1396
|
+
|
|
1397
|
+
- ``names`` -- boolean (default: ``True``);
|
|
1398
|
+
if ``False``, then the facets are given by their indices
|
|
1399
|
+
|
|
1400
|
+
- ``algorithm`` -- string (optional);
|
|
1401
|
+
specify whether the face generator starts with facets or vertices:
|
|
1402
|
+
* ``'primal'`` -- start with the facets
|
|
1403
|
+
* ``'dual'`` -- start with the vertices
|
|
1404
|
+
* ``None`` -- choose automatically
|
|
1405
|
+
|
|
1406
|
+
.. NOTE::
|
|
1407
|
+
|
|
1408
|
+
To compute ridges and f_vector, compute the ridges first.
|
|
1409
|
+
This might be faster.
|
|
1410
|
+
|
|
1411
|
+
EXAMPLES::
|
|
1412
|
+
|
|
1413
|
+
sage: # needs sage.combinat
|
|
1414
|
+
sage: P = polytopes.permutahedron(2)
|
|
1415
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1416
|
+
sage: C.ridges()
|
|
1417
|
+
((An inequality (1, 0) x - 1 >= 0, An inequality (-1, 0) x + 2 >= 0),)
|
|
1418
|
+
sage: C.ridges(add_equations=True)
|
|
1419
|
+
(((An inequality (1, 0) x - 1 >= 0, An equation (1, 1) x - 3 == 0),
|
|
1420
|
+
(An inequality (-1, 0) x + 2 >= 0, An equation (1, 1) x - 3 == 0)),)
|
|
1421
|
+
|
|
1422
|
+
sage: P = polytopes.cyclic_polytope(4,5)
|
|
1423
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1424
|
+
sage: C.ridges()
|
|
1425
|
+
((An inequality (24, -26, 9, -1) x + 0 >= 0,
|
|
1426
|
+
An inequality (-50, 35, -10, 1) x + 24 >= 0),
|
|
1427
|
+
(An inequality (-12, 19, -8, 1) x + 0 >= 0,
|
|
1428
|
+
An inequality (-50, 35, -10, 1) x + 24 >= 0),
|
|
1429
|
+
(An inequality (8, -14, 7, -1) x + 0 >= 0,
|
|
1430
|
+
An inequality (-50, 35, -10, 1) x + 24 >= 0),
|
|
1431
|
+
(An inequality (-6, 11, -6, 1) x + 0 >= 0,
|
|
1432
|
+
An inequality (-50, 35, -10, 1) x + 24 >= 0),
|
|
1433
|
+
(An inequality (-12, 19, -8, 1) x + 0 >= 0,
|
|
1434
|
+
An inequality (24, -26, 9, -1) x + 0 >= 0),
|
|
1435
|
+
(An inequality (8, -14, 7, -1) x + 0 >= 0,
|
|
1436
|
+
An inequality (24, -26, 9, -1) x + 0 >= 0),
|
|
1437
|
+
(An inequality (-6, 11, -6, 1) x + 0 >= 0,
|
|
1438
|
+
An inequality (24, -26, 9, -1) x + 0 >= 0),
|
|
1439
|
+
(An inequality (8, -14, 7, -1) x + 0 >= 0,
|
|
1440
|
+
An inequality (-12, 19, -8, 1) x + 0 >= 0),
|
|
1441
|
+
(An inequality (-6, 11, -6, 1) x + 0 >= 0,
|
|
1442
|
+
An inequality (-12, 19, -8, 1) x + 0 >= 0),
|
|
1443
|
+
(An inequality (-6, 11, -6, 1) x + 0 >= 0,
|
|
1444
|
+
An inequality (8, -14, 7, -1) x + 0 >= 0))
|
|
1445
|
+
sage: C.ridges(names=False)
|
|
1446
|
+
((3, 4),
|
|
1447
|
+
(2, 4),
|
|
1448
|
+
(1, 4),
|
|
1449
|
+
(0, 4),
|
|
1450
|
+
(2, 3),
|
|
1451
|
+
(1, 3),
|
|
1452
|
+
(0, 3),
|
|
1453
|
+
(1, 2),
|
|
1454
|
+
(0, 2),
|
|
1455
|
+
(0, 1))
|
|
1456
|
+
|
|
1457
|
+
sage: P = Polyhedron(rays=[[1,0]])
|
|
1458
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1459
|
+
sage: C
|
|
1460
|
+
A 1-dimensional combinatorial polyhedron with 1 facet
|
|
1461
|
+
sage: C.ridges()
|
|
1462
|
+
()
|
|
1463
|
+
sage: it = C.face_generator(0)
|
|
1464
|
+
sage: for face in it: face.ambient_Hrepresentation()
|
|
1465
|
+
(An inequality (1, 0) x + 0 >= 0, An equation (0, 1) x + 0 == 0)
|
|
1466
|
+
|
|
1467
|
+
TESTS:
|
|
1468
|
+
|
|
1469
|
+
Testing that ``add_equations`` is ignored if ``names`` is ``False``::
|
|
1470
|
+
|
|
1471
|
+
sage: C = CombinatorialPolyhedron(polytopes.simplex())
|
|
1472
|
+
sage: C.ridges(names=False, add_equations=True)
|
|
1473
|
+
((2, 3), (1, 3), (0, 3), (1, 2), (0, 2), (0, 1))
|
|
1474
|
+
"""
|
|
1475
|
+
self._compute_ridges(self._algorithm_to_dual(algorithm))
|
|
1476
|
+
cdef size_t n_ridges = self._ridges.length
|
|
1477
|
+
|
|
1478
|
+
# Mapping the indices of the Vepr to the names, if requested.
|
|
1479
|
+
if self.facet_names() is not None and names is True:
|
|
1480
|
+
def f(size_t i):
|
|
1481
|
+
return self.facet_names()[i]
|
|
1482
|
+
else:
|
|
1483
|
+
def f(size_t i):
|
|
1484
|
+
return smallInteger(i)
|
|
1485
|
+
|
|
1486
|
+
if add_equations and names:
|
|
1487
|
+
return tuple(
|
|
1488
|
+
((f(self._ridges.get(i).first),) + self.equations(),
|
|
1489
|
+
(f(self._ridges.get(i).second),) + self.equations())
|
|
1490
|
+
for i in range(n_ridges))
|
|
1491
|
+
|
|
1492
|
+
return tuple(
|
|
1493
|
+
(f(self._ridges.get(i).first),
|
|
1494
|
+
f(self._ridges.get(i).second))
|
|
1495
|
+
for i in range(n_ridges))
|
|
1496
|
+
|
|
1497
|
+
@cached_method
|
|
1498
|
+
def facet_adjacency_matrix(self, algorithm=None):
|
|
1499
|
+
"""
|
|
1500
|
+
Return the binary matrix of facet adjacencies.
|
|
1501
|
+
|
|
1502
|
+
INPUT:
|
|
1503
|
+
|
|
1504
|
+
- ``algorithm`` -- string (optional);
|
|
1505
|
+
specify whether the face generator starts with facets or vertices:
|
|
1506
|
+
* ``'primal'`` -- start with the facets
|
|
1507
|
+
* ``'dual'`` -- start with the vertices
|
|
1508
|
+
* ``None`` -- choose automatically
|
|
1509
|
+
|
|
1510
|
+
.. SEEALSO::
|
|
1511
|
+
|
|
1512
|
+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.vertex_adjacency_matrix`.
|
|
1513
|
+
|
|
1514
|
+
EXAMPLES::
|
|
1515
|
+
|
|
1516
|
+
sage: P = polytopes.cube()
|
|
1517
|
+
sage: C = P.combinatorial_polyhedron()
|
|
1518
|
+
sage: C.facet_adjacency_matrix()
|
|
1519
|
+
[0 1 1 0 1 1]
|
|
1520
|
+
[1 0 1 1 1 0]
|
|
1521
|
+
[1 1 0 1 0 1]
|
|
1522
|
+
[0 1 1 0 1 1]
|
|
1523
|
+
[1 1 0 1 0 1]
|
|
1524
|
+
[1 0 1 1 1 0]
|
|
1525
|
+
|
|
1526
|
+
TESTS::
|
|
1527
|
+
|
|
1528
|
+
sage: CombinatorialPolyhedron(-1).facet_adjacency_matrix()
|
|
1529
|
+
[]
|
|
1530
|
+
sage: CombinatorialPolyhedron(0).facet_adjacency_matrix()
|
|
1531
|
+
[]
|
|
1532
|
+
sage: polytopes.cube().facet_adjacency_matrix().is_immutable()
|
|
1533
|
+
True
|
|
1534
|
+
"""
|
|
1535
|
+
from sage.rings.integer_ring import ZZ
|
|
1536
|
+
from sage.matrix.constructor import matrix
|
|
1537
|
+
cdef Matrix_dense adjacency_matrix = matrix(
|
|
1538
|
+
ZZ, self.n_facets(), self.n_facets(), 0)
|
|
1539
|
+
cdef size_t i
|
|
1540
|
+
|
|
1541
|
+
self._compute_ridges(self._algorithm_to_dual(algorithm))
|
|
1542
|
+
try:
|
|
1543
|
+
for i in range(self._ridges.length):
|
|
1544
|
+
first = self._ridges.get(i).first
|
|
1545
|
+
second = self._ridges.get(i).second
|
|
1546
|
+
adjacency_matrix.set_unsafe_int(first, second, 1)
|
|
1547
|
+
adjacency_matrix.set_unsafe_int(second, first, 1)
|
|
1548
|
+
except AttributeError:
|
|
1549
|
+
for i in range(self._ridges.length):
|
|
1550
|
+
first = self._ridges.get(i).first
|
|
1551
|
+
second = self._ridges.get(i).second
|
|
1552
|
+
adjacency_matrix[first, second] = adjacency_matrix[second, first] = 1
|
|
1553
|
+
adjacency_matrix.set_immutable()
|
|
1554
|
+
return adjacency_matrix
|
|
1555
|
+
|
|
1556
|
+
def facet_graph(self, names=True, algorithm=None):
|
|
1557
|
+
r"""
|
|
1558
|
+
Return the facet graph.
|
|
1559
|
+
|
|
1560
|
+
The facet graph of a polyhedron consists of
|
|
1561
|
+
ridges as edges and facets as vertices.
|
|
1562
|
+
|
|
1563
|
+
INPUT:
|
|
1564
|
+
|
|
1565
|
+
- ``algorithm`` -- string (optional);
|
|
1566
|
+
specify whether the face generator starts with facets or vertices:
|
|
1567
|
+
|
|
1568
|
+
* ``'primal'`` -- start with the facets
|
|
1569
|
+
* ``'dual'`` -- start with the vertices
|
|
1570
|
+
* ``None`` -- choose automatically
|
|
1571
|
+
|
|
1572
|
+
If ``names`` is ``False``, the ``vertices`` of the graph will
|
|
1573
|
+
be the indices of the facets in the Hrepresentation.
|
|
1574
|
+
|
|
1575
|
+
EXAMPLES::
|
|
1576
|
+
|
|
1577
|
+
sage: P = polytopes.cyclic_polytope(4,6)
|
|
1578
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1579
|
+
sage: C.facet_graph() # needs sage.graphs
|
|
1580
|
+
Graph on 9 vertices
|
|
1581
|
+
|
|
1582
|
+
TESTS::
|
|
1583
|
+
|
|
1584
|
+
sage: P = Polyhedron(ieqs=[[1,-1,0],[1,1,0]])
|
|
1585
|
+
sage: CombinatorialPolyhedron(P).facet_graph() # needs sage.graphs
|
|
1586
|
+
Graph on 2 vertices
|
|
1587
|
+
|
|
1588
|
+
Checking that :issue:`28604` is fixed::
|
|
1589
|
+
|
|
1590
|
+
sage: C = CombinatorialPolyhedron(polytopes.cube()); C
|
|
1591
|
+
A 3-dimensional combinatorial polyhedron with 6 facets
|
|
1592
|
+
sage: C.facet_graph(names=False) # needs sage.graphs
|
|
1593
|
+
Graph on 6 vertices
|
|
1594
|
+
|
|
1595
|
+
sage: C = CombinatorialPolyhedron(polytopes.hypersimplex(5,2)); C
|
|
1596
|
+
A 4-dimensional combinatorial polyhedron with 10 facets
|
|
1597
|
+
sage: C.facet_graph() # needs sage.combinat sage.graphs
|
|
1598
|
+
Graph on 10 vertices
|
|
1599
|
+
"""
|
|
1600
|
+
face_iter = self.face_iter(self.dimension() - 1, algorithm='primal')
|
|
1601
|
+
if names:
|
|
1602
|
+
V = list(facet.ambient_Hrepresentation() for facet in face_iter)
|
|
1603
|
+
else:
|
|
1604
|
+
V = list(facet.ambient_V_indices() for facet in face_iter)
|
|
1605
|
+
E = self.ridges(names=names, add_equations=True, algorithm=algorithm)
|
|
1606
|
+
if not names:
|
|
1607
|
+
# If names is false, the ridges are given as tuple of indices,
|
|
1608
|
+
# i.e. (1,2) instead of (('f1',), ('f2',)).
|
|
1609
|
+
V = list(v[0] for v in V)
|
|
1610
|
+
from sage.graphs.graph import Graph
|
|
1611
|
+
return Graph([V, E], format='vertices_and_edges')
|
|
1612
|
+
|
|
1613
|
+
@cached_method
|
|
1614
|
+
def vertex_facet_graph(self, names=True):
|
|
1615
|
+
r"""
|
|
1616
|
+
Return the vertex-facet graph.
|
|
1617
|
+
|
|
1618
|
+
This method constructs a directed bipartite graph.
|
|
1619
|
+
The nodes of the graph correspond to elements of the Vrepresentation
|
|
1620
|
+
and facets. There is a directed edge from Vrepresentation to facets
|
|
1621
|
+
for each incidence.
|
|
1622
|
+
|
|
1623
|
+
If ``names`` is set to ``False``, then the vertices (of the graph) are given by
|
|
1624
|
+
integers.
|
|
1625
|
+
|
|
1626
|
+
INPUT:
|
|
1627
|
+
|
|
1628
|
+
- ``names`` -- boolean (default: ``True``); if ``True`` label the vertices of the
|
|
1629
|
+
graph by the corresponding names of the Vrepresentation resp. Hrepresentation;
|
|
1630
|
+
if ``False`` label the vertices of the graph by integers
|
|
1631
|
+
|
|
1632
|
+
EXAMPLES::
|
|
1633
|
+
|
|
1634
|
+
sage: P = polytopes.hypercube(2).pyramid()
|
|
1635
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1636
|
+
sage: G = C.vertex_facet_graph(); G # needs sage.graphs
|
|
1637
|
+
Digraph on 10 vertices
|
|
1638
|
+
sage: C.Vrepresentation()
|
|
1639
|
+
(A vertex at (0, -1, -1),
|
|
1640
|
+
A vertex at (0, -1, 1),
|
|
1641
|
+
A vertex at (0, 1, -1),
|
|
1642
|
+
A vertex at (0, 1, 1),
|
|
1643
|
+
A vertex at (1, 0, 0))
|
|
1644
|
+
sage: sorted(G.neighbors_out(C.Vrepresentation()[4])) # needs sage.graphs
|
|
1645
|
+
[An inequality (-1, -1, 0) x + 1 >= 0,
|
|
1646
|
+
An inequality (-1, 0, -1) x + 1 >= 0,
|
|
1647
|
+
An inequality (-1, 0, 1) x + 1 >= 0,
|
|
1648
|
+
An inequality (-1, 1, 0) x + 1 >= 0]
|
|
1649
|
+
|
|
1650
|
+
If ``names`` is ``True`` (the default) but the combinatorial polyhedron
|
|
1651
|
+
has been initialized without specifying names to
|
|
1652
|
+
``Vrepresentation`` and ``Hrepresentation``,
|
|
1653
|
+
then indices of the Vrepresentation and the facets will be used along
|
|
1654
|
+
with a string 'H' or 'V'::
|
|
1655
|
+
|
|
1656
|
+
sage: C = CombinatorialPolyhedron(P.incidence_matrix())
|
|
1657
|
+
sage: C.vertex_facet_graph().vertices(sort=True) # needs sage.graphs
|
|
1658
|
+
[('H', 0),
|
|
1659
|
+
('H', 1),
|
|
1660
|
+
('H', 2),
|
|
1661
|
+
('H', 3),
|
|
1662
|
+
('H', 4),
|
|
1663
|
+
('V', 0),
|
|
1664
|
+
('V', 1),
|
|
1665
|
+
('V', 2),
|
|
1666
|
+
('V', 3),
|
|
1667
|
+
('V', 4)]
|
|
1668
|
+
|
|
1669
|
+
If ``names`` is ``False`` then the vertices of the graph are given by integers::
|
|
1670
|
+
|
|
1671
|
+
sage: C.vertex_facet_graph(names=False).vertices(sort=True) # needs sage.graphs
|
|
1672
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
1673
|
+
|
|
1674
|
+
TESTS:
|
|
1675
|
+
|
|
1676
|
+
Test that :issue:`29898` is fixed::
|
|
1677
|
+
|
|
1678
|
+
sage: Polyhedron().vertex_facet_graph() # needs sage.graphs
|
|
1679
|
+
Digraph on 0 vertices
|
|
1680
|
+
sage: Polyhedron([[0]]).vertex_facet_graph() # needs sage.graphs
|
|
1681
|
+
Digraph on 1 vertex
|
|
1682
|
+
sage: Polyhedron([[0]]).vertex_facet_graph(False) # needs sage.graphs
|
|
1683
|
+
Digraph on 1 vertex
|
|
1684
|
+
"""
|
|
1685
|
+
from sage.graphs.digraph import DiGraph
|
|
1686
|
+
if self.dimension() == -1:
|
|
1687
|
+
return DiGraph()
|
|
1688
|
+
if self.dimension() == 0:
|
|
1689
|
+
if not names:
|
|
1690
|
+
return DiGraph(1)
|
|
1691
|
+
else:
|
|
1692
|
+
Vrep = self.Vrep()
|
|
1693
|
+
if Vrep:
|
|
1694
|
+
v = Vrep[0]
|
|
1695
|
+
else:
|
|
1696
|
+
v = ("V", 0)
|
|
1697
|
+
return DiGraph([[v], []])
|
|
1698
|
+
|
|
1699
|
+
# The face iterator will iterate through the facets in opposite order.
|
|
1700
|
+
facet_iter = self.face_iter(self.dimension() - 1, algorithm='primal')
|
|
1701
|
+
n_facets = self.n_facets()
|
|
1702
|
+
n_Vrep = self.n_Vrepresentation()
|
|
1703
|
+
|
|
1704
|
+
if not names:
|
|
1705
|
+
vertices = [i for i in range(n_facets + n_Vrep)]
|
|
1706
|
+
edges = tuple((j, n_Vrep + n_facets - 1 - i) for i, facet in enumerate(facet_iter) for j in facet.ambient_V_indices())
|
|
1707
|
+
else:
|
|
1708
|
+
facet_names = self.facet_names()
|
|
1709
|
+
if facet_names is None:
|
|
1710
|
+
# No names were provided at initialisation.
|
|
1711
|
+
facet_names = [("H", i) for i in range(n_facets)]
|
|
1712
|
+
|
|
1713
|
+
Vrep = self.Vrep()
|
|
1714
|
+
if Vrep is None:
|
|
1715
|
+
# No names were provided at initialisation.
|
|
1716
|
+
Vrep = [("V", i) for i in range(n_Vrep)]
|
|
1717
|
+
|
|
1718
|
+
vertices = Vrep + facet_names
|
|
1719
|
+
edges = tuple((Vrep[j], facet_names[n_facets - 1 - i]) for i, facet in enumerate(facet_iter) for j in facet.ambient_V_indices())
|
|
1720
|
+
return DiGraph([vertices, edges], format='vertices_and_edges', immutable=True)
|
|
1721
|
+
|
|
1722
|
+
@cached_method
|
|
1723
|
+
def f_vector(self, num_threads=None, parallelization_depth=None, algorithm=None):
|
|
1724
|
+
r"""
|
|
1725
|
+
Compute the ``f_vector`` of the polyhedron.
|
|
1726
|
+
|
|
1727
|
+
The ``f_vector`` contains the number of faces of dimension `k`
|
|
1728
|
+
for each `k` in ``range(-1, self.dimension() + 1)``.
|
|
1729
|
+
|
|
1730
|
+
INPUT:
|
|
1731
|
+
|
|
1732
|
+
- ``num_threads`` -- integer (optional); specify the number of threads
|
|
1733
|
+
|
|
1734
|
+
- ``parallelization_depth`` -- integer (optional); specify
|
|
1735
|
+
how deep in the lattice the parallelization is done
|
|
1736
|
+
|
|
1737
|
+
- ``algorithm`` -- string (optional);
|
|
1738
|
+
specify whether the face generator starts with facets or vertices:
|
|
1739
|
+
|
|
1740
|
+
* ``'primal'`` -- start with the facets
|
|
1741
|
+
* ``'dual'`` -- start with the vertices
|
|
1742
|
+
* ``None`` -- choose automatically
|
|
1743
|
+
|
|
1744
|
+
.. NOTE::
|
|
1745
|
+
|
|
1746
|
+
To obtain edges and/or ridges as well, first do so. This might
|
|
1747
|
+
already compute the ``f_vector``.
|
|
1748
|
+
|
|
1749
|
+
EXAMPLES::
|
|
1750
|
+
|
|
1751
|
+
sage: P = polytopes.permutahedron(5)
|
|
1752
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1753
|
+
sage: C.f_vector()
|
|
1754
|
+
(1, 120, 240, 150, 30, 1)
|
|
1755
|
+
|
|
1756
|
+
sage: P = polytopes.cyclic_polytope(6,10)
|
|
1757
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1758
|
+
sage: C.f_vector()
|
|
1759
|
+
(1, 10, 45, 120, 185, 150, 50, 1)
|
|
1760
|
+
|
|
1761
|
+
Using two threads::
|
|
1762
|
+
|
|
1763
|
+
sage: P = polytopes.permutahedron(5)
|
|
1764
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
1765
|
+
sage: C.f_vector(num_threads=2)
|
|
1766
|
+
(1, 120, 240, 150, 30, 1)
|
|
1767
|
+
|
|
1768
|
+
TESTS::
|
|
1769
|
+
|
|
1770
|
+
sage: type(C.f_vector())
|
|
1771
|
+
<class 'sage.modules.vector_integer_dense.Vector_integer_dense'>
|
|
1772
|
+
"""
|
|
1773
|
+
if num_threads is None:
|
|
1774
|
+
from sage.parallel.ncpus import ncpus
|
|
1775
|
+
num_threads = ncpus()
|
|
1776
|
+
|
|
1777
|
+
if parallelization_depth is None:
|
|
1778
|
+
# Setting some reasonable defaults.
|
|
1779
|
+
if num_threads == 0:
|
|
1780
|
+
parallelization_depth = 0
|
|
1781
|
+
elif num_threads <= 3:
|
|
1782
|
+
parallelization_depth = 1
|
|
1783
|
+
elif num_threads <= 8:
|
|
1784
|
+
parallelization_depth = 2
|
|
1785
|
+
else:
|
|
1786
|
+
parallelization_depth = 3
|
|
1787
|
+
|
|
1788
|
+
if not self._f_vector:
|
|
1789
|
+
self._compute_f_vector(num_threads, parallelization_depth, self._algorithm_to_dual(algorithm))
|
|
1790
|
+
if not self._f_vector:
|
|
1791
|
+
raise ValueError("could not determine f_vector")
|
|
1792
|
+
from sage.modules.free_module_element import vector
|
|
1793
|
+
from sage.rings.integer_ring import ZZ
|
|
1794
|
+
f_vector = vector(ZZ, self._f_vector)
|
|
1795
|
+
f_vector.set_immutable()
|
|
1796
|
+
return f_vector
|
|
1797
|
+
|
|
1798
|
+
def flag_f_vector(self, *args):
|
|
1799
|
+
r"""
|
|
1800
|
+
Return the flag f-vector.
|
|
1801
|
+
|
|
1802
|
+
For each `-1 < i_0 < \dots < i_n < d` the flag f-vector
|
|
1803
|
+
counts the number of flags `F_0 \subset \dots \subset F_n`
|
|
1804
|
+
with `F_j` of dimension `i_j` for each `0 \leq j \leq n`,
|
|
1805
|
+
where `d` is the dimension of the polyhedron.
|
|
1806
|
+
|
|
1807
|
+
INPUT:
|
|
1808
|
+
|
|
1809
|
+
- ``args`` -- integer (optional); specify an entry of the
|
|
1810
|
+
flag-f-vector (must be an increasing sequence of integers)
|
|
1811
|
+
|
|
1812
|
+
OUTPUT:
|
|
1813
|
+
|
|
1814
|
+
- a dictionary, if no arguments were given
|
|
1815
|
+
|
|
1816
|
+
- an integer, if arguments were given
|
|
1817
|
+
|
|
1818
|
+
EXAMPLES:
|
|
1819
|
+
|
|
1820
|
+
Obtain the entire flag-f-vector::
|
|
1821
|
+
|
|
1822
|
+
sage: C = polytopes.hypercube(4).combinatorial_polyhedron()
|
|
1823
|
+
sage: C.flag_f_vector() # needs sage.combinat
|
|
1824
|
+
{(-1,): 1,
|
|
1825
|
+
(0,): 16,
|
|
1826
|
+
(0, 1): 64,
|
|
1827
|
+
(0, 1, 2): 192,
|
|
1828
|
+
(0, 1, 2, 3): 384,
|
|
1829
|
+
(0, 1, 3): 192,
|
|
1830
|
+
(0, 2): 96,
|
|
1831
|
+
(0, 2, 3): 192,
|
|
1832
|
+
(0, 3): 64,
|
|
1833
|
+
(1,): 32,
|
|
1834
|
+
(1, 2): 96,
|
|
1835
|
+
(1, 2, 3): 192,
|
|
1836
|
+
(1, 3): 96,
|
|
1837
|
+
(2,): 24,
|
|
1838
|
+
(2, 3): 48,
|
|
1839
|
+
(3,): 8,
|
|
1840
|
+
(4,): 1}
|
|
1841
|
+
|
|
1842
|
+
Specify an entry::
|
|
1843
|
+
|
|
1844
|
+
sage: C.flag_f_vector(0,3) # needs sage.combinat
|
|
1845
|
+
64
|
|
1846
|
+
sage: C.flag_f_vector(2) # needs sage.combinat
|
|
1847
|
+
24
|
|
1848
|
+
|
|
1849
|
+
Leading ``-1`` and trailing entry of dimension are allowed::
|
|
1850
|
+
|
|
1851
|
+
sage: C.flag_f_vector(-1,0,3) # needs sage.combinat
|
|
1852
|
+
64
|
|
1853
|
+
sage: C.flag_f_vector(-1,0,3,4) # needs sage.combinat
|
|
1854
|
+
64
|
|
1855
|
+
|
|
1856
|
+
One can get the number of trivial faces::
|
|
1857
|
+
|
|
1858
|
+
sage: C.flag_f_vector(-1) # needs sage.combinat
|
|
1859
|
+
1
|
|
1860
|
+
sage: C.flag_f_vector(4) # needs sage.combinat
|
|
1861
|
+
1
|
|
1862
|
+
|
|
1863
|
+
Polyhedra with lines, have ``0`` entries accordingly::
|
|
1864
|
+
|
|
1865
|
+
sage: C = (Polyhedron(lines=[[1]]) * polytopes.hypercube(2)).combinatorial_polyhedron()
|
|
1866
|
+
sage: C.flag_f_vector() # needs sage.combinat
|
|
1867
|
+
{(-1,): 1, (0, 1): 0, (0, 2): 0, (0,): 0, (1, 2): 8, (1,): 4, (2,): 4, 3: 1}
|
|
1868
|
+
|
|
1869
|
+
If the arguments are not strictly increasing or out of range,
|
|
1870
|
+
a key error is raised::
|
|
1871
|
+
|
|
1872
|
+
sage: C.flag_f_vector(-1,0,3,5) # needs sage.combinat
|
|
1873
|
+
Traceback (most recent call last):
|
|
1874
|
+
...
|
|
1875
|
+
KeyError: (0, 3, 5)
|
|
1876
|
+
sage: C.flag_f_vector(-1,3,0) # needs sage.combinat
|
|
1877
|
+
Traceback (most recent call last):
|
|
1878
|
+
...
|
|
1879
|
+
KeyError: (3, 0)
|
|
1880
|
+
"""
|
|
1881
|
+
flag = self._flag_f_vector()
|
|
1882
|
+
if len(args) == 0:
|
|
1883
|
+
return flag
|
|
1884
|
+
elif len(args) == 1:
|
|
1885
|
+
return flag[(args[0],)]
|
|
1886
|
+
else:
|
|
1887
|
+
dim = self.dimension()
|
|
1888
|
+
if args[0] == -1:
|
|
1889
|
+
args = args[1:]
|
|
1890
|
+
if args[-1] == dim:
|
|
1891
|
+
args = args[:-1]
|
|
1892
|
+
return flag[tuple(args)]
|
|
1893
|
+
|
|
1894
|
+
@cached_method
|
|
1895
|
+
def _flag_f_vector(self):
|
|
1896
|
+
r"""
|
|
1897
|
+
Obtain the flag-f-vector from the flag-f-polynomial from the face lattice.
|
|
1898
|
+
|
|
1899
|
+
See :meth:`flag_f_vector`.
|
|
1900
|
+
|
|
1901
|
+
TESTS::
|
|
1902
|
+
|
|
1903
|
+
sage: C = CombinatorialPolyhedron(3)
|
|
1904
|
+
sage: C._flag_f_vector() # needs sage.combinat
|
|
1905
|
+
{(-1,): 1, (0, 1): 0, (0, 2): 0, (0,): 0, (1, 2): 0, (1,): 0, (2,): 0, 3: 1}
|
|
1906
|
+
"""
|
|
1907
|
+
poly = self.face_lattice().flag_f_polynomial()
|
|
1908
|
+
variables = poly.variables()
|
|
1909
|
+
dim = self.dimension()
|
|
1910
|
+
flag = {(smallInteger(-1),): smallInteger(1)}
|
|
1911
|
+
for term in poly.monomials():
|
|
1912
|
+
index = tuple([variables.index(var) for var in term.variables()[:-1]])
|
|
1913
|
+
if index == ():
|
|
1914
|
+
flag[(dim,)] = smallInteger(1)
|
|
1915
|
+
else:
|
|
1916
|
+
flag[index] = poly.monomial_coefficient(term)
|
|
1917
|
+
|
|
1918
|
+
n_lines = sum([1 for x in self.f_vector() if x == 0])
|
|
1919
|
+
if n_lines:
|
|
1920
|
+
# The polyhedron has lines and we have to account for that.
|
|
1921
|
+
# So we basically shift all entries up by the number of lines
|
|
1922
|
+
# and add zero entries for the lines.
|
|
1923
|
+
from itertools import combinations
|
|
1924
|
+
flag_old = flag
|
|
1925
|
+
flag = {(smallInteger(-1),): smallInteger(1)}
|
|
1926
|
+
ran = [smallInteger(i) for i in range(self.dim())]
|
|
1927
|
+
for k in range(1, self.dim()):
|
|
1928
|
+
for comb in combinations(ran, self.dim() - k):
|
|
1929
|
+
if comb[0] < n_lines:
|
|
1930
|
+
# There are no faces of dimension 0,...,n_lines.
|
|
1931
|
+
flag[comb] = smallInteger(0)
|
|
1932
|
+
else:
|
|
1933
|
+
# Shift the old entries up by the number of lines.
|
|
1934
|
+
flag[comb] = flag_old[tuple(i - n_lines for i in comb)]
|
|
1935
|
+
|
|
1936
|
+
flag[self.dimension()] = smallInteger(1)
|
|
1937
|
+
|
|
1938
|
+
return flag
|
|
1939
|
+
|
|
1940
|
+
@cached_method
|
|
1941
|
+
def neighborliness(self):
|
|
1942
|
+
r"""
|
|
1943
|
+
Return the largest ``k``, such that the polyhedron is ``k``-neighborly.
|
|
1944
|
+
|
|
1945
|
+
A polyhedron is `k`-neighborly if every set of `n` vertices forms a face
|
|
1946
|
+
for `n` up to `k`.
|
|
1947
|
+
|
|
1948
|
+
In case of the `d`-dimensional simplex, it returns `d + 1`.
|
|
1949
|
+
|
|
1950
|
+
.. SEEALSO::
|
|
1951
|
+
|
|
1952
|
+
:meth:`is_neighborly`
|
|
1953
|
+
|
|
1954
|
+
EXAMPLES::
|
|
1955
|
+
|
|
1956
|
+
sage: P = polytopes.cyclic_polytope(8,12)
|
|
1957
|
+
sage: C = P.combinatorial_polyhedron()
|
|
1958
|
+
sage: C.neighborliness()
|
|
1959
|
+
4
|
|
1960
|
+
sage: P = polytopes.simplex(6)
|
|
1961
|
+
sage: C = P.combinatorial_polyhedron()
|
|
1962
|
+
sage: C.neighborliness()
|
|
1963
|
+
7
|
|
1964
|
+
sage: P = polytopes.cyclic_polytope(4,10)
|
|
1965
|
+
sage: P = P.join(P)
|
|
1966
|
+
sage: C = P.combinatorial_polyhedron()
|
|
1967
|
+
sage: C.neighborliness()
|
|
1968
|
+
2
|
|
1969
|
+
"""
|
|
1970
|
+
if self.is_simplex():
|
|
1971
|
+
return self.dim() + 1
|
|
1972
|
+
cdef int k = 2
|
|
1973
|
+
f = self.f_vector()
|
|
1974
|
+
while f[k] == self.n_vertices().binomial(k):
|
|
1975
|
+
k += 1
|
|
1976
|
+
return k - 1
|
|
1977
|
+
|
|
1978
|
+
@cached_method
|
|
1979
|
+
def is_neighborly(self, k=None) -> bool:
|
|
1980
|
+
r"""
|
|
1981
|
+
Return whether the polyhedron is neighborly.
|
|
1982
|
+
|
|
1983
|
+
If the input `k` is provided, then return whether the polyhedron is `k`-neighborly.
|
|
1984
|
+
|
|
1985
|
+
A polyhedron is neighborly if every set of `n` vertices forms a face
|
|
1986
|
+
for `n` up to floor of half the dimension of the polyhedron.
|
|
1987
|
+
It is `k`-neighborly if this is true for `n` up to `k`.
|
|
1988
|
+
|
|
1989
|
+
INPUT:
|
|
1990
|
+
|
|
1991
|
+
- ``k`` -- the dimension up to which to check if every set of ``k``
|
|
1992
|
+
vertices forms a face. If no ``k`` is provided, check up to floor
|
|
1993
|
+
of half the dimension of the polyhedron.
|
|
1994
|
+
|
|
1995
|
+
OUTPUT:
|
|
1996
|
+
|
|
1997
|
+
- ``True`` if the every set of up to ``k`` vertices forms a face,
|
|
1998
|
+
- ``False`` otherwise
|
|
1999
|
+
|
|
2000
|
+
.. SEEALSO::
|
|
2001
|
+
|
|
2002
|
+
:meth:`neighborliness`
|
|
2003
|
+
|
|
2004
|
+
EXAMPLES::
|
|
2005
|
+
|
|
2006
|
+
sage: P = polytopes.cyclic_polytope(8,12)
|
|
2007
|
+
sage: C = P.combinatorial_polyhedron()
|
|
2008
|
+
sage: C.is_neighborly()
|
|
2009
|
+
True
|
|
2010
|
+
sage: P = polytopes.simplex(6)
|
|
2011
|
+
sage: C = P.combinatorial_polyhedron()
|
|
2012
|
+
sage: C.is_neighborly()
|
|
2013
|
+
True
|
|
2014
|
+
sage: P = polytopes.cyclic_polytope(4,10)
|
|
2015
|
+
sage: P = P.join(P)
|
|
2016
|
+
sage: C = P.combinatorial_polyhedron()
|
|
2017
|
+
sage: C.is_neighborly()
|
|
2018
|
+
False
|
|
2019
|
+
sage: C.is_neighborly(k=2)
|
|
2020
|
+
True
|
|
2021
|
+
"""
|
|
2022
|
+
from sage.arith.misc import binomial
|
|
2023
|
+
if k is None:
|
|
2024
|
+
k = self.dim() // 2
|
|
2025
|
+
return all(self.f_vector()[i+1] == binomial(self.n_vertices(), i + 1)
|
|
2026
|
+
for i in range(1, k))
|
|
2027
|
+
|
|
2028
|
+
def is_simplex(self) -> bool:
|
|
2029
|
+
r"""
|
|
2030
|
+
Return whether the polyhedron is a simplex.
|
|
2031
|
+
|
|
2032
|
+
A simplex is a bounded polyhedron with `d+1` vertices, where
|
|
2033
|
+
`d` is the dimension.
|
|
2034
|
+
|
|
2035
|
+
EXAMPLES::
|
|
2036
|
+
|
|
2037
|
+
sage: CombinatorialPolyhedron(2).is_simplex()
|
|
2038
|
+
False
|
|
2039
|
+
sage: CombinatorialPolyhedron([[0,1],[0,2],[1,2]]).is_simplex()
|
|
2040
|
+
True
|
|
2041
|
+
"""
|
|
2042
|
+
return self.is_bounded() and (self.dim() + 1 == self.n_vertices())
|
|
2043
|
+
|
|
2044
|
+
@cached_method
|
|
2045
|
+
def is_simplicial(self) -> bool:
|
|
2046
|
+
r"""
|
|
2047
|
+
Test whether the polytope is simplicial.
|
|
2048
|
+
|
|
2049
|
+
This method is not implemented for unbounded polyhedra.
|
|
2050
|
+
|
|
2051
|
+
A polytope is simplicial, if each facet contains exactly `d` vertices,
|
|
2052
|
+
where `d` is the dimension of the polytope.
|
|
2053
|
+
|
|
2054
|
+
EXAMPLES::
|
|
2055
|
+
|
|
2056
|
+
sage: P = polytopes.cyclic_polytope(4,10)
|
|
2057
|
+
sage: C = P.combinatorial_polyhedron()
|
|
2058
|
+
sage: C.is_simplicial()
|
|
2059
|
+
True
|
|
2060
|
+
sage: P = polytopes.hypercube(4)
|
|
2061
|
+
sage: C = P.combinatorial_polyhedron()
|
|
2062
|
+
sage: C.is_simplicial()
|
|
2063
|
+
False
|
|
2064
|
+
|
|
2065
|
+
For unbounded polyhedra, an error is raised::
|
|
2066
|
+
|
|
2067
|
+
sage: C = CombinatorialPolyhedron([[0,1], [0,2]], far_face=[1,2], unbounded=True)
|
|
2068
|
+
sage: C.is_simplicial()
|
|
2069
|
+
Traceback (most recent call last):
|
|
2070
|
+
...
|
|
2071
|
+
NotImplementedError: this function is implemented for polytopes only
|
|
2072
|
+
"""
|
|
2073
|
+
if not self.is_bounded():
|
|
2074
|
+
raise NotImplementedError("this function is implemented for polytopes only")
|
|
2075
|
+
|
|
2076
|
+
cdef ListOfFaces facets = self._bitrep_facets
|
|
2077
|
+
cdef size_t n_facets = facets.n_faces()
|
|
2078
|
+
cdef size_t i
|
|
2079
|
+
cdef int dim = self.dimension()
|
|
2080
|
+
|
|
2081
|
+
for i in range(n_facets):
|
|
2082
|
+
if face_len_atoms(facets.data.faces[i]) != dim:
|
|
2083
|
+
return False
|
|
2084
|
+
return True
|
|
2085
|
+
|
|
2086
|
+
@cached_method
|
|
2087
|
+
def simpliciality(self):
|
|
2088
|
+
r"""
|
|
2089
|
+
Return the largest `k` such that the polytope is `k`-simplicial.
|
|
2090
|
+
|
|
2091
|
+
Return the dimension in case of a simplex.
|
|
2092
|
+
|
|
2093
|
+
A polytope is `k`-simplicial, if every `k`-face is a simplex.
|
|
2094
|
+
|
|
2095
|
+
EXAMPLES::
|
|
2096
|
+
|
|
2097
|
+
sage: cyclic = polytopes.cyclic_polytope(10,4)
|
|
2098
|
+
sage: CombinatorialPolyhedron(cyclic).simpliciality()
|
|
2099
|
+
3
|
|
2100
|
+
|
|
2101
|
+
sage: hypersimplex = polytopes.hypersimplex(5,2)
|
|
2102
|
+
sage: CombinatorialPolyhedron(hypersimplex).simpliciality()
|
|
2103
|
+
2
|
|
2104
|
+
|
|
2105
|
+
sage: cross = polytopes.cross_polytope(4)
|
|
2106
|
+
sage: P = cross.join(cross)
|
|
2107
|
+
sage: CombinatorialPolyhedron(P).simpliciality()
|
|
2108
|
+
3
|
|
2109
|
+
|
|
2110
|
+
sage: P = polytopes.simplex(3)
|
|
2111
|
+
sage: CombinatorialPolyhedron(P).simpliciality()
|
|
2112
|
+
3
|
|
2113
|
+
|
|
2114
|
+
sage: P = polytopes.simplex(1)
|
|
2115
|
+
sage: CombinatorialPolyhedron(P).simpliciality()
|
|
2116
|
+
1
|
|
2117
|
+
|
|
2118
|
+
TESTS::
|
|
2119
|
+
|
|
2120
|
+
sage: P = polytopes.cube()
|
|
2121
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2122
|
+
sage: C.simpliciality is C.simpliciality
|
|
2123
|
+
True
|
|
2124
|
+
"""
|
|
2125
|
+
if not self.is_bounded():
|
|
2126
|
+
raise NotImplementedError("must be bounded")
|
|
2127
|
+
cdef FaceIterator face_iter = self._face_iter(False, -2)
|
|
2128
|
+
cdef int d
|
|
2129
|
+
cdef int dim = self.dimension()
|
|
2130
|
+
|
|
2131
|
+
if self.n_facets() == self.dimension() + 1:
|
|
2132
|
+
# A simplex.
|
|
2133
|
+
return self.dimension()
|
|
2134
|
+
|
|
2135
|
+
cdef simpliciality = dim - 1
|
|
2136
|
+
|
|
2137
|
+
# For each face in the iterator, check if its a simplex.
|
|
2138
|
+
face_iter.structure.lowest_dimension = 2 # every 1-face is a simplex
|
|
2139
|
+
d = face_iter.next_dimension()
|
|
2140
|
+
while d < dim:
|
|
2141
|
+
sig_check()
|
|
2142
|
+
if face_iter.n_atom_rep() == d + 1:
|
|
2143
|
+
# The current face is a simplex.
|
|
2144
|
+
face_iter.ignore_subfaces()
|
|
2145
|
+
else:
|
|
2146
|
+
# Current face is not a simplex.
|
|
2147
|
+
if simpliciality > d - 1:
|
|
2148
|
+
simpliciality = d - 1
|
|
2149
|
+
d = face_iter.next_dimension()
|
|
2150
|
+
if simpliciality == 1:
|
|
2151
|
+
# Every polytope is 1-simplicial.
|
|
2152
|
+
d = dim
|
|
2153
|
+
return smallInteger(simpliciality)
|
|
2154
|
+
|
|
2155
|
+
@cached_method
|
|
2156
|
+
def is_simple(self):
|
|
2157
|
+
r"""
|
|
2158
|
+
Test whether the polytope is simple.
|
|
2159
|
+
|
|
2160
|
+
If the polyhedron is unbounded, return ``False``.
|
|
2161
|
+
|
|
2162
|
+
A polytope is simple, if each vertex is contained in exactly `d` facets,
|
|
2163
|
+
where `d` is the dimension of the polytope.
|
|
2164
|
+
|
|
2165
|
+
EXAMPLES::
|
|
2166
|
+
|
|
2167
|
+
sage: P = polytopes.cyclic_polytope(4,10)
|
|
2168
|
+
sage: C = P.combinatorial_polyhedron()
|
|
2169
|
+
sage: C.is_simple()
|
|
2170
|
+
False
|
|
2171
|
+
sage: P = polytopes.hypercube(4)
|
|
2172
|
+
sage: C = P.combinatorial_polyhedron()
|
|
2173
|
+
sage: C.is_simple()
|
|
2174
|
+
True
|
|
2175
|
+
|
|
2176
|
+
Return ``False`` for unbounded polyhedra::
|
|
2177
|
+
|
|
2178
|
+
sage: C = CombinatorialPolyhedron([[0,1], [0,2]], far_face=[1,2], unbounded=True)
|
|
2179
|
+
sage: C.is_simple()
|
|
2180
|
+
False
|
|
2181
|
+
"""
|
|
2182
|
+
if not self.is_bounded():
|
|
2183
|
+
return False
|
|
2184
|
+
|
|
2185
|
+
cdef ListOfFaces vertices = self._bitrep_Vrep
|
|
2186
|
+
cdef size_t n_vertices = vertices.n_faces()
|
|
2187
|
+
cdef size_t i
|
|
2188
|
+
cdef int dim = self.dimension()
|
|
2189
|
+
|
|
2190
|
+
for i in range(n_vertices):
|
|
2191
|
+
if face_len_atoms(vertices.data.faces[i]) != dim:
|
|
2192
|
+
return False
|
|
2193
|
+
return True
|
|
2194
|
+
|
|
2195
|
+
@cached_method
|
|
2196
|
+
def simplicity(self):
|
|
2197
|
+
r"""
|
|
2198
|
+
Return the largest `k` such that the polytope is `k`-simple.
|
|
2199
|
+
|
|
2200
|
+
Return the dimension in case of a simplex.
|
|
2201
|
+
|
|
2202
|
+
A polytope `P` is `k`-simple, if every `(d-1-k)`-face
|
|
2203
|
+
is contained in exactly `k+1` facets of `P` for `1 \leq k \leq d-1`.
|
|
2204
|
+
|
|
2205
|
+
Equivalently it is `k`-simple if the polar/dual polytope is `k`-simplicial.
|
|
2206
|
+
|
|
2207
|
+
EXAMPLES::
|
|
2208
|
+
|
|
2209
|
+
sage: hyper4 = polytopes.hypersimplex(4,2)
|
|
2210
|
+
sage: CombinatorialPolyhedron(hyper4).simplicity()
|
|
2211
|
+
1
|
|
2212
|
+
|
|
2213
|
+
sage: hyper5 = polytopes.hypersimplex(5,2)
|
|
2214
|
+
sage: CombinatorialPolyhedron(hyper5).simplicity()
|
|
2215
|
+
2
|
|
2216
|
+
|
|
2217
|
+
sage: hyper6 = polytopes.hypersimplex(6,2)
|
|
2218
|
+
sage: CombinatorialPolyhedron(hyper6).simplicity()
|
|
2219
|
+
3
|
|
2220
|
+
|
|
2221
|
+
sage: P = polytopes.simplex(3)
|
|
2222
|
+
sage: CombinatorialPolyhedron(P).simplicity()
|
|
2223
|
+
3
|
|
2224
|
+
|
|
2225
|
+
sage: P = polytopes.simplex(1)
|
|
2226
|
+
sage: CombinatorialPolyhedron(P).simplicity()
|
|
2227
|
+
1
|
|
2228
|
+
|
|
2229
|
+
TESTS::
|
|
2230
|
+
|
|
2231
|
+
sage: P = polytopes.cube()
|
|
2232
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2233
|
+
sage: C.simplicity is C.simplicity
|
|
2234
|
+
True
|
|
2235
|
+
"""
|
|
2236
|
+
if not self.is_bounded():
|
|
2237
|
+
raise NotImplementedError("must be bounded")
|
|
2238
|
+
cdef FaceIterator coface_iter = self._face_iter(True, -2)
|
|
2239
|
+
cdef int d
|
|
2240
|
+
cdef int dim = self.dimension()
|
|
2241
|
+
|
|
2242
|
+
if self.n_facets() == self.dimension() + 1:
|
|
2243
|
+
# A simplex.
|
|
2244
|
+
return self.dimension()
|
|
2245
|
+
|
|
2246
|
+
cdef simplicity = dim - 1
|
|
2247
|
+
|
|
2248
|
+
# For each coface in the iterator, check if its a simplex.
|
|
2249
|
+
coface_iter.structure.lowest_dimension = 2 # every coface of dimension 1 is a simplex
|
|
2250
|
+
d = coface_iter.next_dimension()
|
|
2251
|
+
while d < dim:
|
|
2252
|
+
sig_check()
|
|
2253
|
+
if coface_iter.n_atom_rep() == d + 1:
|
|
2254
|
+
# The current coface is a simplex.
|
|
2255
|
+
coface_iter.ignore_supfaces()
|
|
2256
|
+
else:
|
|
2257
|
+
# Current coface is not a simplex.
|
|
2258
|
+
if simplicity > d - 1:
|
|
2259
|
+
simplicity = d - 1
|
|
2260
|
+
d = coface_iter.next_dimension()
|
|
2261
|
+
if simplicity == 1:
|
|
2262
|
+
# Every polytope is 1-simple.
|
|
2263
|
+
d = dim
|
|
2264
|
+
return smallInteger(simplicity)
|
|
2265
|
+
|
|
2266
|
+
@cached_method
|
|
2267
|
+
def is_lawrence_polytope(self):
|
|
2268
|
+
r"""
|
|
2269
|
+
Return ``True`` if ``self`` is a Lawrence polytope.
|
|
2270
|
+
|
|
2271
|
+
A polytope is called a Lawrence polytope if it has a centrally
|
|
2272
|
+
symmetric (normalized) Gale diagram.
|
|
2273
|
+
|
|
2274
|
+
Equivalently, there exists a partition `P_1,\dots,P_k`
|
|
2275
|
+
of the vertices `V` such that each part
|
|
2276
|
+
`P_i` has size `2` or `1` and for each part there exists
|
|
2277
|
+
a facet with vertices exactly `V \setminus P_i`.
|
|
2278
|
+
|
|
2279
|
+
EXAMPLES::
|
|
2280
|
+
|
|
2281
|
+
sage: C = polytopes.simplex(5).combinatorial_polyhedron()
|
|
2282
|
+
sage: C.is_lawrence_polytope()
|
|
2283
|
+
True
|
|
2284
|
+
sage: P = polytopes.hypercube(4).lawrence_polytope()
|
|
2285
|
+
sage: C = P.combinatorial_polyhedron()
|
|
2286
|
+
sage: C.is_lawrence_polytope()
|
|
2287
|
+
True
|
|
2288
|
+
sage: P = polytopes.hypercube(4)
|
|
2289
|
+
sage: C = P.combinatorial_polyhedron()
|
|
2290
|
+
sage: C.is_lawrence_polytope()
|
|
2291
|
+
False
|
|
2292
|
+
|
|
2293
|
+
For unbounded polyhedra, an error is raised::
|
|
2294
|
+
|
|
2295
|
+
sage: C = CombinatorialPolyhedron([[0,1], [0,2]], far_face=[1,2], unbounded=True)
|
|
2296
|
+
sage: C.is_lawrence_polytope()
|
|
2297
|
+
Traceback (most recent call last):
|
|
2298
|
+
...
|
|
2299
|
+
NotImplementedError: this function is implemented for polytopes only
|
|
2300
|
+
|
|
2301
|
+
AUTHORS:
|
|
2302
|
+
|
|
2303
|
+
- Laith Rastanawi
|
|
2304
|
+
- Jonathan Kliem
|
|
2305
|
+
|
|
2306
|
+
REFERENCES:
|
|
2307
|
+
|
|
2308
|
+
For more information, see [BaSt1990]_.
|
|
2309
|
+
"""
|
|
2310
|
+
if not self.is_compact():
|
|
2311
|
+
raise NotImplementedError("this function is implemented for polytopes only")
|
|
2312
|
+
if self.n_Vrepresentation() <= 2:
|
|
2313
|
+
return True
|
|
2314
|
+
|
|
2315
|
+
cdef FaceIterator facet_iterator = self._face_iter(False, self.dimension()-1)
|
|
2316
|
+
cdef CombinatorialFace facet
|
|
2317
|
+
cdef size_t n_vertices = self.n_Vrepresentation()
|
|
2318
|
+
cdef size_t one, two, length, counter
|
|
2319
|
+
cdef list vertices = [1 for _ in range(n_vertices)]
|
|
2320
|
+
|
|
2321
|
+
for facet in facet_iterator:
|
|
2322
|
+
length = facet.n_atom_rep()
|
|
2323
|
+
if length >= n_vertices - 2:
|
|
2324
|
+
# The facet has at most two non-vertices and corresponds to
|
|
2325
|
+
# two symmetric vertices or a vertex at the origin
|
|
2326
|
+
# in the Gale transform.
|
|
2327
|
+
facet.set_atom_rep()
|
|
2328
|
+
counter = 0
|
|
2329
|
+
while counter < length:
|
|
2330
|
+
if facet.atom_rep[counter] != counter:
|
|
2331
|
+
# We have found our first non-vertex.
|
|
2332
|
+
one = counter
|
|
2333
|
+
break
|
|
2334
|
+
counter += 1
|
|
2335
|
+
else:
|
|
2336
|
+
# The facet contains the first ``length`` vertices.
|
|
2337
|
+
one = length
|
|
2338
|
+
|
|
2339
|
+
if length == n_vertices - 1:
|
|
2340
|
+
# The facet corresponds to a vertex at the origin
|
|
2341
|
+
# of the Gale transform.
|
|
2342
|
+
vertices[one] = 0
|
|
2343
|
+
else:
|
|
2344
|
+
# The facet corresponds to two symmetric vertices
|
|
2345
|
+
# of the Gale transform.
|
|
2346
|
+
while counter < length:
|
|
2347
|
+
if facet.atom_rep[counter] != counter + 1:
|
|
2348
|
+
# We have found our second non-vertex.
|
|
2349
|
+
two = counter + 1
|
|
2350
|
+
break
|
|
2351
|
+
counter += 1
|
|
2352
|
+
else:
|
|
2353
|
+
# The second non-vertex is the very last vertex.
|
|
2354
|
+
two = length + 1
|
|
2355
|
+
|
|
2356
|
+
if vertices[one] == vertices[two]:
|
|
2357
|
+
# Possibly the Gale transform contains duplicates,
|
|
2358
|
+
# we must make sure that the mulitplicites are symmetric as well.
|
|
2359
|
+
# (And not two vertices are symmetric to just one).
|
|
2360
|
+
vertices[one] = 0
|
|
2361
|
+
vertices[two] = 0
|
|
2362
|
+
|
|
2363
|
+
return not any(vertices)
|
|
2364
|
+
|
|
2365
|
+
@cached_method
|
|
2366
|
+
def is_pyramid(self, certificate=False):
|
|
2367
|
+
r"""
|
|
2368
|
+
Test whether the polytope is a pyramid over one of its facets.
|
|
2369
|
+
|
|
2370
|
+
INPUT:
|
|
2371
|
+
|
|
2372
|
+
- ``certificate`` -- boolean (default: ``False``); specifies whether
|
|
2373
|
+
to return a vertex of the polytope which is the apex of a pyramid,
|
|
2374
|
+
if found
|
|
2375
|
+
|
|
2376
|
+
OUTPUT:
|
|
2377
|
+
|
|
2378
|
+
If ``certificate`` is ``True``, returns a tuple containing:
|
|
2379
|
+
|
|
2380
|
+
1. Boolean.
|
|
2381
|
+
2. The apex of the pyramid or ``None``.
|
|
2382
|
+
|
|
2383
|
+
If ``certificate`` is ``False`` returns a boolean.
|
|
2384
|
+
|
|
2385
|
+
AUTHORS:
|
|
2386
|
+
|
|
2387
|
+
- Laith Rastanawi
|
|
2388
|
+
- Jonathan Kliem
|
|
2389
|
+
|
|
2390
|
+
EXAMPLES::
|
|
2391
|
+
|
|
2392
|
+
sage: C = polytopes.cross_polytope(4).combinatorial_polyhedron()
|
|
2393
|
+
sage: C.is_pyramid()
|
|
2394
|
+
False
|
|
2395
|
+
sage: C.is_pyramid(certificate=True)
|
|
2396
|
+
(False, None)
|
|
2397
|
+
sage: C = polytopes.cross_polytope(4).pyramid().combinatorial_polyhedron()
|
|
2398
|
+
sage: C.is_pyramid()
|
|
2399
|
+
True
|
|
2400
|
+
sage: C.is_pyramid(certificate=True)
|
|
2401
|
+
(True, A vertex at (1, 0, 0, 0, 0))
|
|
2402
|
+
sage: C = polytopes.simplex(5).combinatorial_polyhedron()
|
|
2403
|
+
sage: C.is_pyramid(certificate=True)
|
|
2404
|
+
(True, A vertex at (1, 0, 0, 0, 0, 0))
|
|
2405
|
+
|
|
2406
|
+
For unbounded polyhedra, an error is raised::
|
|
2407
|
+
|
|
2408
|
+
sage: C = CombinatorialPolyhedron([[0,1], [0,2]], far_face=[1,2], unbounded=True)
|
|
2409
|
+
sage: C.is_pyramid()
|
|
2410
|
+
Traceback (most recent call last):
|
|
2411
|
+
...
|
|
2412
|
+
ValueError: polyhedron has to be compact
|
|
2413
|
+
|
|
2414
|
+
TESTS::
|
|
2415
|
+
|
|
2416
|
+
sage: CombinatorialPolyhedron(-1).is_pyramid()
|
|
2417
|
+
False
|
|
2418
|
+
sage: CombinatorialPolyhedron(-1).is_pyramid(True)
|
|
2419
|
+
(False, None)
|
|
2420
|
+
sage: CombinatorialPolyhedron(0).is_pyramid()
|
|
2421
|
+
True
|
|
2422
|
+
sage: CombinatorialPolyhedron(0).is_pyramid(True)
|
|
2423
|
+
(True, 0)
|
|
2424
|
+
|
|
2425
|
+
Check that :issue:`30292` is fixed::
|
|
2426
|
+
|
|
2427
|
+
sage: Polyhedron([[0, -1, -1], [0, -1, 1], [0, 1, -1], [0, 1, 1], [1, 0, 0]]).is_pyramid(certificate=True)
|
|
2428
|
+
(True, A vertex at (1, 0, 0))
|
|
2429
|
+
"""
|
|
2430
|
+
if not self.is_bounded():
|
|
2431
|
+
raise ValueError("polyhedron has to be compact")
|
|
2432
|
+
|
|
2433
|
+
if self.dim() == -1:
|
|
2434
|
+
if certificate:
|
|
2435
|
+
return (False, None)
|
|
2436
|
+
return False
|
|
2437
|
+
|
|
2438
|
+
if self.dim() == 0:
|
|
2439
|
+
if certificate:
|
|
2440
|
+
return (True, self.Vrepresentation()[0])
|
|
2441
|
+
return True
|
|
2442
|
+
|
|
2443
|
+
# Find a vertex that is incident to all elements in Hrepresentation but one.
|
|
2444
|
+
vertex_iter = self._face_iter(True, 0)
|
|
2445
|
+
n_facets = self.n_facets()
|
|
2446
|
+
for vertex in vertex_iter:
|
|
2447
|
+
if vertex.n_ambient_Hrepresentation(add_equations=False) == n_facets - 1:
|
|
2448
|
+
if certificate:
|
|
2449
|
+
return (True, vertex.ambient_Vrepresentation()[0])
|
|
2450
|
+
return True
|
|
2451
|
+
|
|
2452
|
+
if certificate:
|
|
2453
|
+
return (False, None)
|
|
2454
|
+
return False
|
|
2455
|
+
|
|
2456
|
+
@cached_method
|
|
2457
|
+
def is_bipyramid(self, certificate=False):
|
|
2458
|
+
r"""
|
|
2459
|
+
Test whether the polytope is a bipyramid over some other polytope.
|
|
2460
|
+
|
|
2461
|
+
INPUT:
|
|
2462
|
+
|
|
2463
|
+
- ``certificate`` -- boolean (default: ``False``); specifies whether
|
|
2464
|
+
to return a vertex of the polytope which is the apex of a pyramid,
|
|
2465
|
+
if found
|
|
2466
|
+
|
|
2467
|
+
INPUT:
|
|
2468
|
+
|
|
2469
|
+
- ``certificate`` -- boolean (default: ``False``); specifies whether
|
|
2470
|
+
to return two vertices of the polytope which are the apices of a
|
|
2471
|
+
bipyramid, if found
|
|
2472
|
+
|
|
2473
|
+
OUTPUT:
|
|
2474
|
+
|
|
2475
|
+
If ``certificate`` is ``True``, returns a tuple containing:
|
|
2476
|
+
|
|
2477
|
+
1. Boolean.
|
|
2478
|
+
2. ``None`` or a tuple containing:
|
|
2479
|
+
a. The first apex.
|
|
2480
|
+
b. The second apex.
|
|
2481
|
+
|
|
2482
|
+
If ``certificate`` is ``False`` returns a boolean.
|
|
2483
|
+
|
|
2484
|
+
EXAMPLES::
|
|
2485
|
+
|
|
2486
|
+
sage: C = polytopes.hypercube(4).combinatorial_polyhedron()
|
|
2487
|
+
sage: C.is_bipyramid()
|
|
2488
|
+
False
|
|
2489
|
+
sage: C.is_bipyramid(certificate=True)
|
|
2490
|
+
(False, None)
|
|
2491
|
+
sage: C = polytopes.cross_polytope(4).combinatorial_polyhedron()
|
|
2492
|
+
sage: C.is_bipyramid()
|
|
2493
|
+
True
|
|
2494
|
+
sage: C.is_bipyramid(certificate=True)
|
|
2495
|
+
(True, [A vertex at (1, 0, 0, 0), A vertex at (-1, 0, 0, 0)])
|
|
2496
|
+
|
|
2497
|
+
For unbounded polyhedra, an error is raised::
|
|
2498
|
+
|
|
2499
|
+
sage: C = CombinatorialPolyhedron([[0,1], [0,2]], far_face=[1,2], unbounded=True)
|
|
2500
|
+
sage: C.is_pyramid()
|
|
2501
|
+
Traceback (most recent call last):
|
|
2502
|
+
...
|
|
2503
|
+
ValueError: polyhedron has to be compact
|
|
2504
|
+
|
|
2505
|
+
TESTS::
|
|
2506
|
+
|
|
2507
|
+
sage: CombinatorialPolyhedron(-1).is_bipyramid()
|
|
2508
|
+
False
|
|
2509
|
+
sage: CombinatorialPolyhedron(-1).is_bipyramid(True)
|
|
2510
|
+
(False, None)
|
|
2511
|
+
sage: C = polytopes.cross_polytope(1)
|
|
2512
|
+
sage: C.is_bipyramid()
|
|
2513
|
+
True
|
|
2514
|
+
sage: C.is_bipyramid(True)
|
|
2515
|
+
(True, [A vertex at (1), A vertex at (-1)])
|
|
2516
|
+
|
|
2517
|
+
Check that bug analog to :issue:`30292` is avoided::
|
|
2518
|
+
|
|
2519
|
+
sage: Polyhedron([[0, 1, 0], [0, 0, 1], [0, -1, -1], [1, 0, 0], [-1, 0, 0]]).is_bipyramid(certificate=True)
|
|
2520
|
+
(True, [A vertex at (1, 0, 0), A vertex at (-1, 0, 0)])
|
|
2521
|
+
|
|
2522
|
+
ALGORITHM:
|
|
2523
|
+
|
|
2524
|
+
Assume all faces of a polyhedron to be given as lists of vertices.
|
|
2525
|
+
|
|
2526
|
+
A polytope is a bipyramid with apexes `v`, `w` if and only if for each
|
|
2527
|
+
proper face `v \in F` there exists a face `G` with
|
|
2528
|
+
`G \setminus \{w\} = F \setminus \{v\}`
|
|
2529
|
+
and vice versa (for each proper face
|
|
2530
|
+
`w \in F` there exists ...).
|
|
2531
|
+
|
|
2532
|
+
To check this property it suffices to check for all facets of the polyhedron.
|
|
2533
|
+
"""
|
|
2534
|
+
if not self.is_compact():
|
|
2535
|
+
|
|
2536
|
+
raise ValueError("polyhedron has to be compact")
|
|
2537
|
+
|
|
2538
|
+
n_facets = self.n_facets()
|
|
2539
|
+
if n_facets % 2 or self.dim() < 1:
|
|
2540
|
+
if certificate:
|
|
2541
|
+
return (False, None)
|
|
2542
|
+
return False
|
|
2543
|
+
|
|
2544
|
+
facets_incidences = [set(f) for f in self.facets(names=False)]
|
|
2545
|
+
verts_incidences = dict()
|
|
2546
|
+
for v in self.face_iter(0):
|
|
2547
|
+
verts_incidences[v.ambient_V_indices()[0]] = set(v.ambient_H_indices(add_equations=False))
|
|
2548
|
+
|
|
2549
|
+
# Find two vertices ``vert1`` and ``vert2`` such that one of them
|
|
2550
|
+
# lies on exactly half of the facets, and the other one lies on
|
|
2551
|
+
# exactly the other half.
|
|
2552
|
+
from itertools import combinations
|
|
2553
|
+
for index1, index2 in combinations(verts_incidences, 2):
|
|
2554
|
+
vert1_incidences = verts_incidences[index1]
|
|
2555
|
+
vert2_incidences = verts_incidences[index2]
|
|
2556
|
+
vert1and2 = vert1_incidences.union(vert2_incidences)
|
|
2557
|
+
if len(vert1and2) == n_facets:
|
|
2558
|
+
# We have found two candidates for apexes.
|
|
2559
|
+
# Remove from each facet ``index1`` resp. ``index2``.
|
|
2560
|
+
test_facets = set(frozenset(facet_inc.difference({index1, index2}))
|
|
2561
|
+
for facet_inc in facets_incidences)
|
|
2562
|
+
if len(test_facets) == n_facets/2:
|
|
2563
|
+
# For each `F` containing `index1` there is
|
|
2564
|
+
# `G` containing `index2` such that
|
|
2565
|
+
# `F \setminus \{index1\} = G \setminus \{index2\}
|
|
2566
|
+
# and vice versa.
|
|
2567
|
+
if certificate:
|
|
2568
|
+
V = self.vertices()
|
|
2569
|
+
return (True, [V[index1], V[index2]])
|
|
2570
|
+
return True
|
|
2571
|
+
|
|
2572
|
+
if certificate:
|
|
2573
|
+
return (False, None)
|
|
2574
|
+
return False
|
|
2575
|
+
|
|
2576
|
+
@cached_method
|
|
2577
|
+
def is_prism(self, certificate=False):
|
|
2578
|
+
r"""
|
|
2579
|
+
Test whether the polytope is a prism of some polytope.
|
|
2580
|
+
|
|
2581
|
+
INPUT:
|
|
2582
|
+
|
|
2583
|
+
- ``certificate`` -- boolean (default: ``False``); specifies whether
|
|
2584
|
+
to return two facets of the polytope which are the bases of a prism,
|
|
2585
|
+
if found
|
|
2586
|
+
|
|
2587
|
+
OUTPUT:
|
|
2588
|
+
|
|
2589
|
+
If ``certificate`` is ``True``, returns a tuple containing:
|
|
2590
|
+
|
|
2591
|
+
1. Boolean.
|
|
2592
|
+
2. ``None`` or a tuple containing:
|
|
2593
|
+
a. List of the vertices of the first base facet.
|
|
2594
|
+
b. List of the vertices of the second base facet.
|
|
2595
|
+
|
|
2596
|
+
If ``certificate`` is ``False`` returns a boolean.
|
|
2597
|
+
|
|
2598
|
+
TESTS::
|
|
2599
|
+
|
|
2600
|
+
sage: CombinatorialPolyhedron(-1).is_prism()
|
|
2601
|
+
False
|
|
2602
|
+
sage: CombinatorialPolyhedron(1).is_prism()
|
|
2603
|
+
False
|
|
2604
|
+
sage: C = polytopes.cross_polytope(3).prism().combinatorial_polyhedron()
|
|
2605
|
+
sage: C.is_prism(certificate=True)
|
|
2606
|
+
(True,
|
|
2607
|
+
[(A vertex at (0, 0, 1, 0),
|
|
2608
|
+
A vertex at (0, 1, 0, 0),
|
|
2609
|
+
A vertex at (0, 0, 0, -1),
|
|
2610
|
+
A vertex at (0, 0, -1, 0),
|
|
2611
|
+
A vertex at (0, -1, 0, 0),
|
|
2612
|
+
A vertex at (0, 0, 0, 1)),
|
|
2613
|
+
(A vertex at (1, 1, 0, 0),
|
|
2614
|
+
A vertex at (1, 0, 0, -1),
|
|
2615
|
+
A vertex at (1, 0, -1, 0),
|
|
2616
|
+
A vertex at (1, -1, 0, 0),
|
|
2617
|
+
A vertex at (1, 0, 0, 1),
|
|
2618
|
+
A vertex at (1, 0, 1, 0))])
|
|
2619
|
+
sage: C = CombinatorialPolyhedron([[0,1], [0,2]], far_face=[1,2], unbounded=True)
|
|
2620
|
+
sage: C.is_prism()
|
|
2621
|
+
Traceback (most recent call last):
|
|
2622
|
+
...
|
|
2623
|
+
ValueError: self must be bounded
|
|
2624
|
+
"""
|
|
2625
|
+
if not certificate:
|
|
2626
|
+
return self.dual().is_bipyramid()
|
|
2627
|
+
|
|
2628
|
+
val, cert = self.dual().is_bipyramid(True)
|
|
2629
|
+
if val:
|
|
2630
|
+
facets = self.facets()
|
|
2631
|
+
return (True, [facets[cert[0]], facets[cert[1]]])
|
|
2632
|
+
|
|
2633
|
+
return (False, None)
|
|
2634
|
+
|
|
2635
|
+
def join_of_Vrep(self, *indices):
|
|
2636
|
+
r"""
|
|
2637
|
+
Return the smallest face containing all Vrepresentatives indicated by the indices.
|
|
2638
|
+
|
|
2639
|
+
.. SEEALSO::
|
|
2640
|
+
|
|
2641
|
+
:meth:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator_base.join_of_Vrep`.
|
|
2642
|
+
|
|
2643
|
+
EXAMPLES::
|
|
2644
|
+
|
|
2645
|
+
sage: # needs sage.combinat
|
|
2646
|
+
sage: P = polytopes.permutahedron(4)
|
|
2647
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2648
|
+
sage: C.join_of_Vrep(0,1)
|
|
2649
|
+
A 1-dimensional face of a 3-dimensional combinatorial polyhedron
|
|
2650
|
+
sage: C.join_of_Vrep(0,11).ambient_V_indices()
|
|
2651
|
+
(0, 1, 10, 11, 12, 13)
|
|
2652
|
+
sage: C.join_of_Vrep(8).ambient_V_indices()
|
|
2653
|
+
(8,)
|
|
2654
|
+
sage: C.join_of_Vrep().ambient_V_indices()
|
|
2655
|
+
()
|
|
2656
|
+
"""
|
|
2657
|
+
return self.face_generator().join_of_Vrep(*indices)
|
|
2658
|
+
|
|
2659
|
+
def meet_of_Hrep(self, *indices):
|
|
2660
|
+
r"""
|
|
2661
|
+
Return the largest face contained in all facets indicated by the indices.
|
|
2662
|
+
|
|
2663
|
+
.. SEEALSO::
|
|
2664
|
+
|
|
2665
|
+
:meth:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator_base.meet_of_Hrep`.
|
|
2666
|
+
|
|
2667
|
+
EXAMPLES::
|
|
2668
|
+
|
|
2669
|
+
sage: # needs sage.groups sage.rings.number_field
|
|
2670
|
+
sage: P = polytopes.dodecahedron()
|
|
2671
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2672
|
+
sage: C.meet_of_Hrep(0)
|
|
2673
|
+
A 2-dimensional face of a 3-dimensional combinatorial polyhedron
|
|
2674
|
+
sage: C.meet_of_Hrep(0).ambient_H_indices()
|
|
2675
|
+
(0,)
|
|
2676
|
+
sage: C.meet_of_Hrep(0,1).ambient_H_indices()
|
|
2677
|
+
(0, 1)
|
|
2678
|
+
sage: C.meet_of_Hrep(0,2).ambient_H_indices()
|
|
2679
|
+
(0, 2)
|
|
2680
|
+
sage: C.meet_of_Hrep(0,2,3).ambient_H_indices()
|
|
2681
|
+
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
|
|
2682
|
+
sage: C.meet_of_Hrep().ambient_H_indices()
|
|
2683
|
+
()
|
|
2684
|
+
"""
|
|
2685
|
+
return self.face_generator().meet_of_Hrep(*indices)
|
|
2686
|
+
|
|
2687
|
+
def face_generator(self, dimension=None, algorithm=None):
|
|
2688
|
+
r"""
|
|
2689
|
+
Iterator over all proper faces of specified dimension.
|
|
2690
|
+
|
|
2691
|
+
INPUT:
|
|
2692
|
+
|
|
2693
|
+
- ``dimension`` -- if specified, then iterate over only this dimension
|
|
2694
|
+
|
|
2695
|
+
- ``algorithm`` -- string (optional);
|
|
2696
|
+
specify whether the face generator starts with facets or vertices:
|
|
2697
|
+
|
|
2698
|
+
* ``'primal'`` -- start with the facets
|
|
2699
|
+
* ``'dual'`` -- start with the vertices
|
|
2700
|
+
* ``None`` -- choose automatically
|
|
2701
|
+
|
|
2702
|
+
OUTPUT: :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator`
|
|
2703
|
+
|
|
2704
|
+
.. NOTE::
|
|
2705
|
+
|
|
2706
|
+
:class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator`
|
|
2707
|
+
can ignore subfaces or supfaces of the current face.
|
|
2708
|
+
|
|
2709
|
+
EXAMPLES::
|
|
2710
|
+
|
|
2711
|
+
sage: # needs sage.combinat
|
|
2712
|
+
sage: P = polytopes.permutahedron(5)
|
|
2713
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2714
|
+
sage: it = C.face_generator(dimension=2)
|
|
2715
|
+
sage: face = next(it); face
|
|
2716
|
+
A 2-dimensional face of a 4-dimensional combinatorial polyhedron
|
|
2717
|
+
sage: face.ambient_Vrepresentation()
|
|
2718
|
+
(A vertex at (1, 3, 2, 5, 4),
|
|
2719
|
+
A vertex at (2, 3, 1, 5, 4),
|
|
2720
|
+
A vertex at (3, 1, 2, 5, 4),
|
|
2721
|
+
A vertex at (3, 2, 1, 5, 4),
|
|
2722
|
+
A vertex at (2, 1, 3, 5, 4),
|
|
2723
|
+
A vertex at (1, 2, 3, 5, 4))
|
|
2724
|
+
sage: face = next(it); face
|
|
2725
|
+
A 2-dimensional face of a 4-dimensional combinatorial polyhedron
|
|
2726
|
+
sage: face.ambient_Vrepresentation()
|
|
2727
|
+
(A vertex at (2, 1, 4, 5, 3),
|
|
2728
|
+
A vertex at (3, 2, 4, 5, 1),
|
|
2729
|
+
A vertex at (3, 1, 4, 5, 2),
|
|
2730
|
+
A vertex at (1, 3, 4, 5, 2),
|
|
2731
|
+
A vertex at (1, 2, 4, 5, 3),
|
|
2732
|
+
A vertex at (2, 3, 4, 5, 1))
|
|
2733
|
+
sage: face.ambient_Hrepresentation()
|
|
2734
|
+
(An inequality (0, 0, -1, -1, 0) x + 9 >= 0,
|
|
2735
|
+
An inequality (0, 0, 0, -1, 0) x + 5 >= 0,
|
|
2736
|
+
An equation (1, 1, 1, 1, 1) x - 15 == 0)
|
|
2737
|
+
sage: face.ambient_H_indices()
|
|
2738
|
+
(25, 29, 30)
|
|
2739
|
+
sage: face = next(it); face
|
|
2740
|
+
A 2-dimensional face of a 4-dimensional combinatorial polyhedron
|
|
2741
|
+
sage: face.ambient_H_indices()
|
|
2742
|
+
(24, 29, 30)
|
|
2743
|
+
sage: face.ambient_V_indices()
|
|
2744
|
+
(32, 89, 90, 94)
|
|
2745
|
+
|
|
2746
|
+
sage: C = CombinatorialPolyhedron([[0,1,2],[0,1,3],[0,2,3],[1,2,3]])
|
|
2747
|
+
sage: it = C.face_generator()
|
|
2748
|
+
sage: for face in it: face.ambient_Vrepresentation()
|
|
2749
|
+
(1, 2, 3)
|
|
2750
|
+
(0, 2, 3)
|
|
2751
|
+
(0, 1, 3)
|
|
2752
|
+
(0, 1, 2)
|
|
2753
|
+
(2, 3)
|
|
2754
|
+
(1, 3)
|
|
2755
|
+
(1, 2)
|
|
2756
|
+
(3,)
|
|
2757
|
+
(2,)
|
|
2758
|
+
(1,)
|
|
2759
|
+
(0, 3)
|
|
2760
|
+
(0, 2)
|
|
2761
|
+
(0,)
|
|
2762
|
+
(0, 1)
|
|
2763
|
+
|
|
2764
|
+
sage: P = Polyhedron(rays=[[1,0],[0,1]], vertices=[[1,0],[0,1]])
|
|
2765
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2766
|
+
sage: it = C.face_generator(1)
|
|
2767
|
+
sage: for face in it: face.ambient_Vrepresentation()
|
|
2768
|
+
(A vertex at (0, 1), A vertex at (1, 0))
|
|
2769
|
+
(A ray in the direction (1, 0), A vertex at (1, 0))
|
|
2770
|
+
(A ray in the direction (0, 1), A vertex at (0, 1))
|
|
2771
|
+
|
|
2772
|
+
.. SEEALSO::
|
|
2773
|
+
|
|
2774
|
+
:class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator`,
|
|
2775
|
+
:class:`~sage.geometry.polyhedron.combinatorial_polyhedron.combinatorial_face.CombinatorialFace`.
|
|
2776
|
+
"""
|
|
2777
|
+
cdef int dual
|
|
2778
|
+
|
|
2779
|
+
dual = self._algorithm_to_dual(algorithm)
|
|
2780
|
+
|
|
2781
|
+
if dual == -1:
|
|
2782
|
+
# Determine the faster way, to iterate through all faces.
|
|
2783
|
+
if not self.is_bounded() or self.n_facets() <= self.n_Vrepresentation():
|
|
2784
|
+
dual = 0
|
|
2785
|
+
else:
|
|
2786
|
+
dual = 1
|
|
2787
|
+
|
|
2788
|
+
return FaceIterator(self, dual, output_dimension=dimension)
|
|
2789
|
+
|
|
2790
|
+
face_iter = face_generator
|
|
2791
|
+
|
|
2792
|
+
cdef FaceIterator _face_iter(self, bint dual, int dimension):
|
|
2793
|
+
r"""
|
|
2794
|
+
A method to obtain the FaceIterator as Cython object.
|
|
2795
|
+
|
|
2796
|
+
``dimension`` is the ``output_dimension`` of
|
|
2797
|
+
:class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator`.
|
|
2798
|
+
If ``dimension == -2`` this will indicate no ``output_dimension``.
|
|
2799
|
+
|
|
2800
|
+
See :meth:`CombinatorialPolyhedron.face_iter`
|
|
2801
|
+
"""
|
|
2802
|
+
if dual and not self.is_bounded():
|
|
2803
|
+
raise ValueError("cannot iterate over dual of unbounded polyhedron")
|
|
2804
|
+
if dimension == -2:
|
|
2805
|
+
return FaceIterator(self, dual)
|
|
2806
|
+
else:
|
|
2807
|
+
return FaceIterator(self, dual, output_dimension=dimension)
|
|
2808
|
+
|
|
2809
|
+
def face_lattice(self):
|
|
2810
|
+
r"""
|
|
2811
|
+
Generate the face-lattice.
|
|
2812
|
+
|
|
2813
|
+
OUTPUT: :class:`~sage.combinat.posets.lattices.FiniteLatticePoset`
|
|
2814
|
+
|
|
2815
|
+
.. NOTE::
|
|
2816
|
+
|
|
2817
|
+
Use :meth:`CombinatorialPolyhedron.face_by_face_lattice_index` to get
|
|
2818
|
+
the face for each index.
|
|
2819
|
+
|
|
2820
|
+
.. WARNING::
|
|
2821
|
+
|
|
2822
|
+
The labeling of the face lattice might depend on architecture
|
|
2823
|
+
and implementation. Relabeling the face lattice with
|
|
2824
|
+
:meth:`CombinatorialPolyhedron.face_by_face_lattice_index` or
|
|
2825
|
+
the properties obtained from this face will be platform independent.
|
|
2826
|
+
|
|
2827
|
+
EXAMPLES::
|
|
2828
|
+
|
|
2829
|
+
sage: P = Polyhedron(rays=[[1,0],[0,1]])
|
|
2830
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2831
|
+
sage: C.face_lattice() # needs sage.combinat
|
|
2832
|
+
Finite lattice containing 5 elements
|
|
2833
|
+
|
|
2834
|
+
sage: P = Polyhedron(rays=[[1,0,0], [-1,0,0], [0,-1,0], [0,1,0]])
|
|
2835
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2836
|
+
sage: P1 = Polyhedron(rays=[[1,0], [-1,0]])
|
|
2837
|
+
sage: C1 = CombinatorialPolyhedron(P1)
|
|
2838
|
+
sage: C.face_lattice().is_isomorphic(C1.face_lattice()) # needs sage.combinat
|
|
2839
|
+
True
|
|
2840
|
+
|
|
2841
|
+
sage: P = polytopes.permutahedron(5)
|
|
2842
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2843
|
+
sage: C.face_lattice() # needs sage.combinat
|
|
2844
|
+
Finite lattice containing 542 elements
|
|
2845
|
+
|
|
2846
|
+
TESTS::
|
|
2847
|
+
|
|
2848
|
+
sage: P = polytopes.cyclic_polytope(4,10)
|
|
2849
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2850
|
+
sage: C.face_lattice().is_isomorphic(P.face_lattice()) # needs sage.combinat
|
|
2851
|
+
True
|
|
2852
|
+
|
|
2853
|
+
sage: P = polytopes.permutahedron(4)
|
|
2854
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2855
|
+
sage: C.face_lattice().is_isomorphic(P.face_lattice()) # needs sage.combinat
|
|
2856
|
+
True
|
|
2857
|
+
"""
|
|
2858
|
+
from sage.combinat.posets.lattices import FiniteLatticePoset
|
|
2859
|
+
return FiniteLatticePoset(self.hasse_diagram())
|
|
2860
|
+
|
|
2861
|
+
@cached_method
|
|
2862
|
+
def hasse_diagram(self):
|
|
2863
|
+
r"""
|
|
2864
|
+
Return the Hasse diagram of ``self``.
|
|
2865
|
+
|
|
2866
|
+
This is the Hasse diagram of the poset of the faces of ``self``:
|
|
2867
|
+
A directed graph consisting of a vertex for each face
|
|
2868
|
+
and an edge for each minimal inclusion of faces.
|
|
2869
|
+
|
|
2870
|
+
.. NOTE::
|
|
2871
|
+
|
|
2872
|
+
The vertices of the Hasse diagram are given by indices.
|
|
2873
|
+
Use :meth:`CombinatorialPolyhedron.face_by_face_lattice_index`
|
|
2874
|
+
to relabel.
|
|
2875
|
+
|
|
2876
|
+
.. WARNING::
|
|
2877
|
+
|
|
2878
|
+
The indices of the Hasse diagram might depend on architecture
|
|
2879
|
+
and implementation. Relabeling the face lattice with
|
|
2880
|
+
:meth:`CombinatorialPolyhedron.face_by_face_lattice_index` or
|
|
2881
|
+
the properties obtained from this face will be platform independent
|
|
2882
|
+
|
|
2883
|
+
EXAMPLES::
|
|
2884
|
+
|
|
2885
|
+
sage: # needs sage.graphs sage.rings.number_field
|
|
2886
|
+
sage: P = polytopes.regular_polygon(4).pyramid()
|
|
2887
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2888
|
+
sage: D = C.hasse_diagram(); D
|
|
2889
|
+
Digraph on 20 vertices
|
|
2890
|
+
sage: D.average_degree()
|
|
2891
|
+
21/5
|
|
2892
|
+
sage: D.relabel(C.face_by_face_lattice_index)
|
|
2893
|
+
sage: dim_0_vert = D.vertices(sort=True)[1:6]; dim_0_vert
|
|
2894
|
+
[A 0-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
2895
|
+
A 0-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
2896
|
+
A 0-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
2897
|
+
A 0-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
2898
|
+
A 0-dimensional face of a 3-dimensional combinatorial polyhedron]
|
|
2899
|
+
sage: sorted(D.out_degree(vertices=dim_0_vert))
|
|
2900
|
+
[3, 3, 3, 3, 4]
|
|
2901
|
+
"""
|
|
2902
|
+
if not self._face_lattice_incidences:
|
|
2903
|
+
# compute all incidences.
|
|
2904
|
+
self._compute_face_lattice_incidences()
|
|
2905
|
+
if self._face_lattice_incidences is None:
|
|
2906
|
+
raise TypeError("could not determine face lattice")
|
|
2907
|
+
|
|
2908
|
+
# Edges of the face-lattice/Hasse diagram.
|
|
2909
|
+
cdef size_t j
|
|
2910
|
+
cdef size_t n_incidences = self._face_lattice_incidences.length
|
|
2911
|
+
edges = tuple(self._face_lattice_incidences[j] for j in range(n_incidences))
|
|
2912
|
+
|
|
2913
|
+
V = tuple(smallInteger(i) for i in range(sum(self._f_vector)))
|
|
2914
|
+
|
|
2915
|
+
from sage.graphs.digraph import DiGraph
|
|
2916
|
+
D = DiGraph([V, edges], format='vertices_and_edges', vertex_labels=False)
|
|
2917
|
+
return D
|
|
2918
|
+
|
|
2919
|
+
def _face_lattice_dimension(self, index):
|
|
2920
|
+
r"""
|
|
2921
|
+
Return for each element in :meth:`CombinatorialPolyhedron.face_lattice`
|
|
2922
|
+
its dimension.
|
|
2923
|
+
|
|
2924
|
+
EXAMPLES::
|
|
2925
|
+
|
|
2926
|
+
sage: P = polytopes.cube()
|
|
2927
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2928
|
+
sage: F = C.face_lattice() # needs sage.combinat
|
|
2929
|
+
sage: def f(i):
|
|
2930
|
+
....: return (i, C._face_lattice_dimension(i))
|
|
2931
|
+
....:
|
|
2932
|
+
sage: G = F.relabel(f) # needs sage.combinat
|
|
2933
|
+
sage: set(G._elements) # needs sage.combinat
|
|
2934
|
+
{(0, -1),
|
|
2935
|
+
(1, 0),
|
|
2936
|
+
(2, 0),
|
|
2937
|
+
(3, 0),
|
|
2938
|
+
(4, 0),
|
|
2939
|
+
(5, 0),
|
|
2940
|
+
(6, 0),
|
|
2941
|
+
(7, 0),
|
|
2942
|
+
(8, 0),
|
|
2943
|
+
(9, 1),
|
|
2944
|
+
(10, 1),
|
|
2945
|
+
(11, 1),
|
|
2946
|
+
(12, 1),
|
|
2947
|
+
(13, 1),
|
|
2948
|
+
(14, 1),
|
|
2949
|
+
(15, 1),
|
|
2950
|
+
(16, 1),
|
|
2951
|
+
(17, 1),
|
|
2952
|
+
(18, 1),
|
|
2953
|
+
(19, 1),
|
|
2954
|
+
(20, 1),
|
|
2955
|
+
(21, 2),
|
|
2956
|
+
(22, 2),
|
|
2957
|
+
(23, 2),
|
|
2958
|
+
(24, 2),
|
|
2959
|
+
(25, 2),
|
|
2960
|
+
(26, 2),
|
|
2961
|
+
(27, 3)}
|
|
2962
|
+
"""
|
|
2963
|
+
f_vector = self.f_vector()
|
|
2964
|
+
dim = self.dimension()
|
|
2965
|
+
|
|
2966
|
+
# Getting the dimension, by considering the following:
|
|
2967
|
+
# The level-set of dimension `d` will have indices `k, k+1, ..., k+n-1`,
|
|
2968
|
+
# where `n` is the number of faces of dimension `d` ( ``n = f_vector[d + 1]``)
|
|
2969
|
+
# and `k` is the number of face of dimension up to `d`, i.e.
|
|
2970
|
+
# ``k = sum(f_vector[:d])``.
|
|
2971
|
+
return max(d for d in range(dim+2) if sum(f_vector[:d]) <= index) - 1
|
|
2972
|
+
|
|
2973
|
+
def face_by_face_lattice_index(self, index):
|
|
2974
|
+
r"""
|
|
2975
|
+
Return the element of :meth:`CombinatorialPolyhedron.face_lattice` with corresponding index.
|
|
2976
|
+
|
|
2977
|
+
The element will be returned as
|
|
2978
|
+
:class:`~sage.geometry.polyhedron.combinatorial_polyhedron.combinatorial_face.CombinatorialFace`.
|
|
2979
|
+
|
|
2980
|
+
EXAMPLES::
|
|
2981
|
+
|
|
2982
|
+
sage: # needs sage.combinat
|
|
2983
|
+
sage: P = polytopes.cube()
|
|
2984
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
2985
|
+
sage: F = C.face_lattice()
|
|
2986
|
+
sage: F
|
|
2987
|
+
Finite lattice containing 28 elements
|
|
2988
|
+
sage: G = F.relabel(C.face_by_face_lattice_index)
|
|
2989
|
+
sage: G.level_sets()[0]
|
|
2990
|
+
[A -1-dimensional face of a 3-dimensional combinatorial polyhedron]
|
|
2991
|
+
sage: G.level_sets()[3]
|
|
2992
|
+
[A 2-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
2993
|
+
A 2-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
2994
|
+
A 2-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
2995
|
+
A 2-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
2996
|
+
A 2-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
2997
|
+
A 2-dimensional face of a 3-dimensional combinatorial polyhedron]
|
|
2998
|
+
|
|
2999
|
+
sage: P = Polyhedron(rays=[[0,1], [1,0]])
|
|
3000
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
3001
|
+
sage: F = C.face_lattice() # needs sage.combinat
|
|
3002
|
+
sage: G = F.relabel(C.face_by_face_lattice_index) # needs sage.combinat
|
|
3003
|
+
sage: G._elements # needs sage.combinat
|
|
3004
|
+
(A -1-dimensional face of a 2-dimensional combinatorial polyhedron,
|
|
3005
|
+
A 0-dimensional face of a 2-dimensional combinatorial polyhedron,
|
|
3006
|
+
A 1-dimensional face of a 2-dimensional combinatorial polyhedron,
|
|
3007
|
+
A 1-dimensional face of a 2-dimensional combinatorial polyhedron,
|
|
3008
|
+
A 2-dimensional face of a 2-dimensional combinatorial polyhedron)
|
|
3009
|
+
|
|
3010
|
+
sage: def f(i): return C.face_by_face_lattice_index(i).ambient_V_indices()
|
|
3011
|
+
sage: G = F.relabel(f) # needs sage.combinat
|
|
3012
|
+
sage: G._elements # needs sage.combinat
|
|
3013
|
+
((), (0,), (0, 1), (0, 2), (0, 1, 2))
|
|
3014
|
+
"""
|
|
3015
|
+
self._record_all_faces() # Initialize ``_all_faces``, if not done yet.
|
|
3016
|
+
dim = self._face_lattice_dimension(index) # Determine dimension to that index.
|
|
3017
|
+
newindex = index - sum(self._f_vector[:dim + 1]) # Index in that level-set.
|
|
3018
|
+
|
|
3019
|
+
# Let ``_all_faces`` determine Vrepresentation.
|
|
3020
|
+
return self._all_faces.get_face(dim, newindex)
|
|
3021
|
+
|
|
3022
|
+
def a_maximal_chain(self, Vindex=None, Hindex=None):
|
|
3023
|
+
r"""
|
|
3024
|
+
Return a maximal chain of the face lattice in increasing order
|
|
3025
|
+
without empty face and whole polyhedron/maximal face.
|
|
3026
|
+
|
|
3027
|
+
INPUT:
|
|
3028
|
+
|
|
3029
|
+
- ``Vindex`` -- integer (default: ``None``); prescribe the index of the vertex in the chain
|
|
3030
|
+
- ``Hindex`` -- integer (default: ``None``); prescribe the index of the facet in the chain
|
|
3031
|
+
|
|
3032
|
+
Each face is given as
|
|
3033
|
+
:class:`~sage.geometry.polyhedron.combinatorial_polyhedron.combinatorial_face.CombinatorialFace`.
|
|
3034
|
+
|
|
3035
|
+
EXAMPLES::
|
|
3036
|
+
|
|
3037
|
+
sage: P = polytopes.cross_polytope(4)
|
|
3038
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3039
|
+
sage: chain = C.a_maximal_chain(); chain
|
|
3040
|
+
[A 0-dimensional face of a 4-dimensional combinatorial polyhedron,
|
|
3041
|
+
A 1-dimensional face of a 4-dimensional combinatorial polyhedron,
|
|
3042
|
+
A 2-dimensional face of a 4-dimensional combinatorial polyhedron,
|
|
3043
|
+
A 3-dimensional face of a 4-dimensional combinatorial polyhedron]
|
|
3044
|
+
sage: [face.ambient_V_indices() for face in chain]
|
|
3045
|
+
[(7,), (6, 7), (5, 6, 7), (4, 5, 6, 7)]
|
|
3046
|
+
|
|
3047
|
+
sage: P = polytopes.hypercube(4)
|
|
3048
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3049
|
+
sage: chain = C.a_maximal_chain(); chain
|
|
3050
|
+
[A 0-dimensional face of a 4-dimensional combinatorial polyhedron,
|
|
3051
|
+
A 1-dimensional face of a 4-dimensional combinatorial polyhedron,
|
|
3052
|
+
A 2-dimensional face of a 4-dimensional combinatorial polyhedron,
|
|
3053
|
+
A 3-dimensional face of a 4-dimensional combinatorial polyhedron]
|
|
3054
|
+
sage: [face.ambient_V_indices() for face in chain]
|
|
3055
|
+
[(15,), (6, 15), (5, 6, 14, 15), (0, 5, 6, 7, 8, 9, 14, 15)]
|
|
3056
|
+
|
|
3057
|
+
sage: # needs sage.combinat
|
|
3058
|
+
sage: P = polytopes.permutahedron(4)
|
|
3059
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3060
|
+
sage: chain = C.a_maximal_chain(); chain
|
|
3061
|
+
[A 0-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
3062
|
+
A 1-dimensional face of a 3-dimensional combinatorial polyhedron,
|
|
3063
|
+
A 2-dimensional face of a 3-dimensional combinatorial polyhedron]
|
|
3064
|
+
sage: [face.ambient_V_indices() for face in chain]
|
|
3065
|
+
[(16,), (15, 16), (8, 9, 14, 15, 16, 17)]
|
|
3066
|
+
|
|
3067
|
+
sage: P = Polyhedron(rays=[[1,0]], lines=[[0,1]])
|
|
3068
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3069
|
+
sage: chain = C.a_maximal_chain()
|
|
3070
|
+
sage: [face.ambient_V_indices() for face in chain]
|
|
3071
|
+
[(0, 1)]
|
|
3072
|
+
|
|
3073
|
+
sage: P = Polyhedron(rays=[[1,0,0],[0,0,1]], lines=[[0,1,0]])
|
|
3074
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3075
|
+
sage: chain = C.a_maximal_chain()
|
|
3076
|
+
sage: [face.ambient_V_indices() for face in chain]
|
|
3077
|
+
[(0, 1), (0, 1, 3)]
|
|
3078
|
+
|
|
3079
|
+
sage: P = Polyhedron(rays=[[1,0,0]], lines=[[0,1,0],[0,0,1]])
|
|
3080
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3081
|
+
sage: chain = C.a_maximal_chain()
|
|
3082
|
+
sage: [face.ambient_V_indices() for face in chain]
|
|
3083
|
+
[(0, 1, 2)]
|
|
3084
|
+
|
|
3085
|
+
Specify an index for the vertex of the chain::
|
|
3086
|
+
|
|
3087
|
+
sage: P = polytopes.cube()
|
|
3088
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3089
|
+
sage: [face.ambient_V_indices() for face in C.a_maximal_chain()]
|
|
3090
|
+
[(5,), (0, 5), (0, 3, 4, 5)]
|
|
3091
|
+
sage: [face.ambient_V_indices() for face in C.a_maximal_chain(Vindex=2)]
|
|
3092
|
+
[(2,), (2, 7), (2, 3, 4, 7)]
|
|
3093
|
+
|
|
3094
|
+
Specify an index for the facet of the chain::
|
|
3095
|
+
|
|
3096
|
+
sage: [face.ambient_H_indices() for face in C.a_maximal_chain()]
|
|
3097
|
+
[(3, 4, 5), (4, 5), (5,)]
|
|
3098
|
+
sage: [face.ambient_H_indices() for face in C.a_maximal_chain(Hindex=3)]
|
|
3099
|
+
[(3, 4, 5), (3, 4), (3,)]
|
|
3100
|
+
sage: [face.ambient_H_indices() for face in C.a_maximal_chain(Hindex=2)]
|
|
3101
|
+
[(2, 3, 5), (2, 3), (2,)]
|
|
3102
|
+
|
|
3103
|
+
If the specified vertex is not contained in the specified facet an error is raised::
|
|
3104
|
+
|
|
3105
|
+
sage: C.a_maximal_chain(Vindex=0, Hindex=3)
|
|
3106
|
+
Traceback (most recent call last):
|
|
3107
|
+
...
|
|
3108
|
+
ValueError: the given Vindex is not compatible with the given Hindex
|
|
3109
|
+
|
|
3110
|
+
An error is raised, if the specified index does not correspond to a facet::
|
|
3111
|
+
|
|
3112
|
+
sage: C.a_maximal_chain(Hindex=40)
|
|
3113
|
+
Traceback (most recent call last):
|
|
3114
|
+
...
|
|
3115
|
+
ValueError: the given Hindex does not correspond to a facet
|
|
3116
|
+
|
|
3117
|
+
An error is raised, if the specified index does not correspond to a vertex::
|
|
3118
|
+
|
|
3119
|
+
sage: C.a_maximal_chain(Vindex=40)
|
|
3120
|
+
Traceback (most recent call last):
|
|
3121
|
+
...
|
|
3122
|
+
ValueError: the given Vindex does not correspond to a vertex
|
|
3123
|
+
|
|
3124
|
+
::
|
|
3125
|
+
|
|
3126
|
+
sage: P = Polyhedron(rays=[[1,0,0],[0,0,1]], lines=[[0,1,0]])
|
|
3127
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3128
|
+
sage: C.a_maximal_chain(Vindex=0)
|
|
3129
|
+
Traceback (most recent call last):
|
|
3130
|
+
...
|
|
3131
|
+
ValueError: the given Vindex does not correspond to a vertex
|
|
3132
|
+
|
|
3133
|
+
::
|
|
3134
|
+
|
|
3135
|
+
sage: P = Polyhedron(rays=[[1,0,0],[0,0,1]])
|
|
3136
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3137
|
+
sage: C.a_maximal_chain(Vindex=0)
|
|
3138
|
+
[A 0-dimensional face of a 2-dimensional combinatorial polyhedron,
|
|
3139
|
+
A 1-dimensional face of a 2-dimensional combinatorial polyhedron]
|
|
3140
|
+
sage: C.a_maximal_chain(Vindex=1)
|
|
3141
|
+
Traceback (most recent call last):
|
|
3142
|
+
...
|
|
3143
|
+
ValueError: the given Vindex does not correspond to a vertex
|
|
3144
|
+
"""
|
|
3145
|
+
if self.n_facets() == 0 or self.dimension() == 0:
|
|
3146
|
+
return []
|
|
3147
|
+
|
|
3148
|
+
# We take a face iterator and do one depth-search.
|
|
3149
|
+
# Depending on whether it is dual or not,
|
|
3150
|
+
# the search will be from the top or bottom.
|
|
3151
|
+
cdef FaceIterator it = self.face_generator()
|
|
3152
|
+
chain = [None]*(self.dimension())
|
|
3153
|
+
dual = it.dual
|
|
3154
|
+
final_dim = 0 if not dual else self.dimension()-1
|
|
3155
|
+
|
|
3156
|
+
cdef bint found_Vindex = Vindex is None
|
|
3157
|
+
cdef bint found_Hindex = Hindex is None
|
|
3158
|
+
|
|
3159
|
+
# For each dimension we save the first face we see.
|
|
3160
|
+
# This is the face whose sub-/supfaces we visit in the next step.
|
|
3161
|
+
current_dim = self.dimension()
|
|
3162
|
+
for face in it:
|
|
3163
|
+
if not found_Hindex:
|
|
3164
|
+
if Hindex not in face.ambient_H_indices():
|
|
3165
|
+
continue
|
|
3166
|
+
if face.dimension() == self.dimension() - 1:
|
|
3167
|
+
found_Hindex = True
|
|
3168
|
+
if not found_Vindex and Vindex not in face.ambient_V_indices():
|
|
3169
|
+
raise ValueError("the given Vindex is not compatible with the given Hindex")
|
|
3170
|
+
if not found_Vindex:
|
|
3171
|
+
if Vindex not in face.ambient_V_indices():
|
|
3172
|
+
continue
|
|
3173
|
+
if face.dimension() == 0:
|
|
3174
|
+
found_Vindex = True
|
|
3175
|
+
if not found_Hindex and Hindex not in face.ambient_H_indices():
|
|
3176
|
+
raise ValueError("the given Vindex is not compatible with the given Hindex")
|
|
3177
|
+
|
|
3178
|
+
it.only_subsets()
|
|
3179
|
+
current_dim = face.dimension()
|
|
3180
|
+
chain[current_dim] = face
|
|
3181
|
+
|
|
3182
|
+
if found_Vindex is False:
|
|
3183
|
+
raise ValueError("the given Vindex does not correspond to a vertex")
|
|
3184
|
+
if found_Hindex is False:
|
|
3185
|
+
raise ValueError("the given Hindex does not correspond to a facet")
|
|
3186
|
+
|
|
3187
|
+
if current_dim != final_dim:
|
|
3188
|
+
# The polyhedron contains lines.
|
|
3189
|
+
# Note that the iterator was always not dual
|
|
3190
|
+
# in this case.
|
|
3191
|
+
return chain[current_dim:]
|
|
3192
|
+
return chain
|
|
3193
|
+
|
|
3194
|
+
def _test_a_maximal_chain(self, tester=None, **options):
|
|
3195
|
+
"""
|
|
3196
|
+
Run tests on the method :meth:`.a_maximal_chain`.
|
|
3197
|
+
|
|
3198
|
+
TESTS::
|
|
3199
|
+
|
|
3200
|
+
sage: polytopes.cross_polytope(3).combinatorial_polyhedron()._test_a_maximal_chain()
|
|
3201
|
+
"""
|
|
3202
|
+
if tester is None:
|
|
3203
|
+
tester = self._tester(**options)
|
|
3204
|
+
|
|
3205
|
+
def test_a_chain(b):
|
|
3206
|
+
for i in range(len(b) - 1):
|
|
3207
|
+
tester.assertTrue(b[i].is_subface(b[i+1]))
|
|
3208
|
+
|
|
3209
|
+
if self.is_bounded():
|
|
3210
|
+
b = self.a_maximal_chain()
|
|
3211
|
+
test_a_chain(b)
|
|
3212
|
+
if not self.n_vertices():
|
|
3213
|
+
return
|
|
3214
|
+
|
|
3215
|
+
from sage.misc.prandom import randrange
|
|
3216
|
+
|
|
3217
|
+
if self.n_vertices():
|
|
3218
|
+
# We obtain a chain containing a random vertex.
|
|
3219
|
+
i = randrange(self.n_vertices())
|
|
3220
|
+
b = self.a_maximal_chain(Vindex=i)
|
|
3221
|
+
test_a_chain(b)
|
|
3222
|
+
tester.assertTrue(all(i in f.ambient_V_indices() for f in b))
|
|
3223
|
+
|
|
3224
|
+
if self.n_facets():
|
|
3225
|
+
# We obtain a chain containing a random facet.
|
|
3226
|
+
i = randrange(self.n_facets())
|
|
3227
|
+
b = self.a_maximal_chain(Hindex=i)
|
|
3228
|
+
test_a_chain(b)
|
|
3229
|
+
tester.assertTrue(all(i in f.ambient_H_indices() for f in b))
|
|
3230
|
+
|
|
3231
|
+
# We obtain a chain containing that facet
|
|
3232
|
+
# and a random vertex contained in it.
|
|
3233
|
+
facet = self.facets(names=False)[i]
|
|
3234
|
+
j = facet[randrange(len(facet))]
|
|
3235
|
+
b = self.a_maximal_chain(Vindex=j, Hindex=i)
|
|
3236
|
+
test_a_chain(b)
|
|
3237
|
+
tester.assertTrue(all(j in f.ambient_V_indices() for f in b))
|
|
3238
|
+
tester.assertTrue(all(i in f.ambient_H_indices() for f in b))
|
|
3239
|
+
|
|
3240
|
+
cdef tuple Vrep(self):
|
|
3241
|
+
r"""
|
|
3242
|
+
Return the names of the Vrepresentation, if they exist. Else return ``None``.
|
|
3243
|
+
"""
|
|
3244
|
+
return self._Vrep
|
|
3245
|
+
|
|
3246
|
+
cdef tuple facet_names(self):
|
|
3247
|
+
r"""
|
|
3248
|
+
Return the names Hrepresentatives, which are facets.
|
|
3249
|
+
|
|
3250
|
+
If not given, return ``None``.
|
|
3251
|
+
"""
|
|
3252
|
+
return self._facet_names
|
|
3253
|
+
|
|
3254
|
+
cdef tuple equations(self):
|
|
3255
|
+
r"""
|
|
3256
|
+
Return the names of the equations.
|
|
3257
|
+
|
|
3258
|
+
If not equations are given, return ``None``.
|
|
3259
|
+
"""
|
|
3260
|
+
return self._equations
|
|
3261
|
+
|
|
3262
|
+
cdef unsigned int n_Vrepresentation(self) noexcept:
|
|
3263
|
+
r"""
|
|
3264
|
+
Return the number of elements in the Vrepresentation.
|
|
3265
|
+
"""
|
|
3266
|
+
return self._n_Vrepresentation
|
|
3267
|
+
|
|
3268
|
+
cdef unsigned int n_Hrepresentation(self) noexcept:
|
|
3269
|
+
r"""
|
|
3270
|
+
Return the number of elements in the Hrepresentation.
|
|
3271
|
+
"""
|
|
3272
|
+
return self._n_Hrepresentation
|
|
3273
|
+
|
|
3274
|
+
def is_compact(self):
|
|
3275
|
+
r"""
|
|
3276
|
+
Return whether the polyhedron is compact.
|
|
3277
|
+
|
|
3278
|
+
EXAMPLES::
|
|
3279
|
+
|
|
3280
|
+
sage: C = CombinatorialPolyhedron([[0,1], [0,2]], far_face=[1,2], unbounded=True)
|
|
3281
|
+
sage: C.is_compact()
|
|
3282
|
+
False
|
|
3283
|
+
sage: C = CombinatorialPolyhedron([[0,1], [0,2], [1,2]])
|
|
3284
|
+
sage: C.is_compact()
|
|
3285
|
+
True
|
|
3286
|
+
sage: P = polytopes.simplex()
|
|
3287
|
+
sage: P.combinatorial_polyhedron().is_compact()
|
|
3288
|
+
True
|
|
3289
|
+
sage: P = Polyhedron(rays=P.vertices())
|
|
3290
|
+
sage: P.combinatorial_polyhedron().is_compact()
|
|
3291
|
+
False
|
|
3292
|
+
"""
|
|
3293
|
+
return self.is_bounded()
|
|
3294
|
+
|
|
3295
|
+
cdef bint is_bounded(self) noexcept:
|
|
3296
|
+
r"""
|
|
3297
|
+
Return whether the polyhedron is bounded.
|
|
3298
|
+
"""
|
|
3299
|
+
return self._bounded
|
|
3300
|
+
|
|
3301
|
+
cdef ListOfFaces bitrep_facets(self):
|
|
3302
|
+
r"""
|
|
3303
|
+
Return the facets in bit representation.
|
|
3304
|
+
"""
|
|
3305
|
+
return self._bitrep_facets
|
|
3306
|
+
|
|
3307
|
+
cdef ListOfFaces bitrep_Vrep(self):
|
|
3308
|
+
r"""
|
|
3309
|
+
Return the Vrepresentations in bit representation.
|
|
3310
|
+
"""
|
|
3311
|
+
return self._bitrep_Vrep
|
|
3312
|
+
|
|
3313
|
+
cdef tuple far_face_tuple(self):
|
|
3314
|
+
r"""
|
|
3315
|
+
Return the far face as it was given on initialization.
|
|
3316
|
+
"""
|
|
3317
|
+
return self._far_face_tuple
|
|
3318
|
+
|
|
3319
|
+
def __eq__(self, other):
|
|
3320
|
+
r"""
|
|
3321
|
+
Return whether ``self`` and ``other`` are equal.
|
|
3322
|
+
"""
|
|
3323
|
+
if not isinstance(other, CombinatorialPolyhedron):
|
|
3324
|
+
return False
|
|
3325
|
+
cdef CombinatorialPolyhedron other_C = other
|
|
3326
|
+
return (self.n_facets() == other.n_facets()
|
|
3327
|
+
and self.Vrepresentation() == other.Vrepresentation()
|
|
3328
|
+
and self.facet_names() == other_C.facet_names()
|
|
3329
|
+
and self.equations() == other_C.equations()
|
|
3330
|
+
and self.dimension() == other.dimension()
|
|
3331
|
+
and self.far_face_tuple() == other_C.far_face_tuple()
|
|
3332
|
+
and self.incidence_matrix() == other.incidence_matrix())
|
|
3333
|
+
|
|
3334
|
+
# Methods to obtain a different combinatorial polyhedron.
|
|
3335
|
+
|
|
3336
|
+
cpdef CombinatorialPolyhedron dual(self):
|
|
3337
|
+
r"""
|
|
3338
|
+
Return the dual/polar of ``self``.
|
|
3339
|
+
|
|
3340
|
+
Only defined for bounded polyhedra.
|
|
3341
|
+
|
|
3342
|
+
.. SEEALSO::
|
|
3343
|
+
|
|
3344
|
+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.polar`.
|
|
3345
|
+
|
|
3346
|
+
EXAMPLES::
|
|
3347
|
+
|
|
3348
|
+
sage: P = polytopes.cube()
|
|
3349
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3350
|
+
sage: D = C.dual()
|
|
3351
|
+
sage: D.f_vector()
|
|
3352
|
+
(1, 6, 12, 8, 1)
|
|
3353
|
+
sage: D1 = P.polar().combinatorial_polyhedron()
|
|
3354
|
+
sage: D1.face_lattice().is_isomorphic(D.face_lattice()) # needs sage.combinat
|
|
3355
|
+
True
|
|
3356
|
+
|
|
3357
|
+
Polar is an alias to be consistent with :class:`~sage.geometry.polyhedron.base.Polyhedron_base`::
|
|
3358
|
+
|
|
3359
|
+
sage: C.polar().f_vector()
|
|
3360
|
+
(1, 6, 12, 8, 1)
|
|
3361
|
+
|
|
3362
|
+
For unbounded polyhedra, an error is raised::
|
|
3363
|
+
|
|
3364
|
+
sage: C = CombinatorialPolyhedron([[0,1], [0,2]], far_face=[1,2], unbounded=True)
|
|
3365
|
+
sage: C.dual()
|
|
3366
|
+
Traceback (most recent call last):
|
|
3367
|
+
...
|
|
3368
|
+
ValueError: self must be bounded
|
|
3369
|
+
"""
|
|
3370
|
+
if not self.is_bounded():
|
|
3371
|
+
raise ValueError("self must be bounded")
|
|
3372
|
+
cdef ListOfFaces new_facets = self.bitrep_Vrep().__copy__()
|
|
3373
|
+
cdef ListOfFaces new_Vrep = self.bitrep_facets().__copy__()
|
|
3374
|
+
|
|
3375
|
+
return CombinatorialPolyhedron((new_facets, new_Vrep))
|
|
3376
|
+
|
|
3377
|
+
polar = dual
|
|
3378
|
+
|
|
3379
|
+
cpdef CombinatorialPolyhedron pyramid(self, new_vertex=None, new_facet=None):
|
|
3380
|
+
r"""
|
|
3381
|
+
Return the pyramid of ``self``.
|
|
3382
|
+
|
|
3383
|
+
INPUT:
|
|
3384
|
+
|
|
3385
|
+
- ``new_vertex`` -- (optional); specify a new vertex name to set up
|
|
3386
|
+
the pyramid with vertex names
|
|
3387
|
+
- ``new_facet`` -- (optional); specify a new facet name to set up
|
|
3388
|
+
the pyramid with facet names
|
|
3389
|
+
|
|
3390
|
+
EXAMPLES::
|
|
3391
|
+
|
|
3392
|
+
sage: C = CombinatorialPolyhedron(((1,2,3),(1,2,4),(1,3,4),(2,3,4)))
|
|
3393
|
+
sage: C1 = C.pyramid()
|
|
3394
|
+
sage: C1.facets()
|
|
3395
|
+
((0, 1, 2, 4), (0, 1, 3, 4), (0, 2, 3, 4), (1, 2, 3, 4), (0, 1, 2, 3))
|
|
3396
|
+
|
|
3397
|
+
::
|
|
3398
|
+
|
|
3399
|
+
sage: P = polytopes.cube()
|
|
3400
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
3401
|
+
sage: C1 = C.pyramid()
|
|
3402
|
+
sage: P1 = P.pyramid()
|
|
3403
|
+
sage: C2 = P1.combinatorial_polyhedron()
|
|
3404
|
+
sage: C2.vertex_facet_graph().is_isomorphic(C1.vertex_facet_graph()) # needs sage.combinat
|
|
3405
|
+
True
|
|
3406
|
+
|
|
3407
|
+
One can specify a name for the new vertex::
|
|
3408
|
+
|
|
3409
|
+
sage: P = polytopes.cyclic_polytope(4,10)
|
|
3410
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3411
|
+
sage: C1 = C.pyramid(new_vertex='apex')
|
|
3412
|
+
sage: C1.is_pyramid(certificate=True)
|
|
3413
|
+
(True, 'apex')
|
|
3414
|
+
sage: C1.facets()[0]
|
|
3415
|
+
(A vertex at (0, 0, 0, 0),
|
|
3416
|
+
A vertex at (1, 1, 1, 1),
|
|
3417
|
+
A vertex at (2, 4, 8, 16),
|
|
3418
|
+
A vertex at (3, 9, 27, 81),
|
|
3419
|
+
'apex')
|
|
3420
|
+
|
|
3421
|
+
One can specify a name for the new facets::
|
|
3422
|
+
|
|
3423
|
+
sage: # needs sage.rings.number_field
|
|
3424
|
+
sage: P = polytopes.regular_polygon(4)
|
|
3425
|
+
sage: C = P.combinatorial_polyhedron()
|
|
3426
|
+
sage: C1 = C.pyramid(new_facet='base')
|
|
3427
|
+
sage: C1.Hrepresentation()
|
|
3428
|
+
(An inequality (-1/2, 1/2) x + 1/2 >= 0,
|
|
3429
|
+
An inequality (-1/2, -1/2) x + 1/2 >= 0,
|
|
3430
|
+
An inequality (1/2, 0.50000000000000000?) x + 1/2 >= 0,
|
|
3431
|
+
An inequality (1/2, -1/2) x + 1/2 >= 0,
|
|
3432
|
+
'base')
|
|
3433
|
+
|
|
3434
|
+
For unbounded polyhedra, an error is raised::
|
|
3435
|
+
|
|
3436
|
+
sage: C = CombinatorialPolyhedron([[0,1], [0,2]], far_face=[1,2], unbounded=True)
|
|
3437
|
+
sage: C.pyramid()
|
|
3438
|
+
Traceback (most recent call last):
|
|
3439
|
+
...
|
|
3440
|
+
ValueError: self must be bounded
|
|
3441
|
+
"""
|
|
3442
|
+
if not self.is_bounded():
|
|
3443
|
+
raise ValueError("self must be bounded")
|
|
3444
|
+
cdef ListOfFaces new_facets = self.bitrep_facets().pyramid()
|
|
3445
|
+
cdef ListOfFaces new_Vrep = self.bitrep_Vrep().pyramid()
|
|
3446
|
+
|
|
3447
|
+
if new_vertex is not None:
|
|
3448
|
+
new_Vrep_names = self.Vrepresentation() + (new_vertex,)
|
|
3449
|
+
else:
|
|
3450
|
+
new_Vrep_names = None
|
|
3451
|
+
|
|
3452
|
+
if new_facet is not None:
|
|
3453
|
+
if self.facet_names() is not None:
|
|
3454
|
+
new_facet_names = self.facet_names() + (new_facet,)
|
|
3455
|
+
else:
|
|
3456
|
+
# Closures inside cpdef functions not yet supported
|
|
3457
|
+
new_facet_names = self.Hrepresentation()[:self.n_facets()] + (new_facet,)
|
|
3458
|
+
else:
|
|
3459
|
+
new_facet_names = None
|
|
3460
|
+
|
|
3461
|
+
return CombinatorialPolyhedron((new_facets, new_Vrep), Vrep=new_Vrep_names, facets=new_facet_names)
|
|
3462
|
+
|
|
3463
|
+
# Internal methods.
|
|
3464
|
+
|
|
3465
|
+
cdef int _compute_f_vector(self, size_t num_threads, size_t parallelization_depth, int dual) except -1:
|
|
3466
|
+
r"""
|
|
3467
|
+
Compute the ``f_vector`` of the polyhedron.
|
|
3468
|
+
|
|
3469
|
+
See :meth:`f_vector`.
|
|
3470
|
+
"""
|
|
3471
|
+
if self._f_vector:
|
|
3472
|
+
return 0 # There is no need to recompute the f_vector.
|
|
3473
|
+
|
|
3474
|
+
cdef int dim = self.dimension()
|
|
3475
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
|
3476
|
+
|
|
3477
|
+
if num_threads == 0:
|
|
3478
|
+
# No need to complain.
|
|
3479
|
+
num_threads = 1
|
|
3480
|
+
|
|
3481
|
+
if parallelization_depth > dim - 1:
|
|
3482
|
+
# Is a very bad choice anyway, but prevent segmentation faults.
|
|
3483
|
+
parallelization_depth = dim - 1
|
|
3484
|
+
|
|
3485
|
+
if dual == -1:
|
|
3486
|
+
if not self.is_bounded() or self.n_facets() <= self.n_Vrepresentation():
|
|
3487
|
+
# In this case the non-dual approach is faster.
|
|
3488
|
+
dual = 0
|
|
3489
|
+
else:
|
|
3490
|
+
# In this case the dual approach is faster.
|
|
3491
|
+
dual = 1
|
|
3492
|
+
|
|
3493
|
+
cdef FaceIterator face_iter
|
|
3494
|
+
cdef iter_t* structs = <iter_t*> mem.allocarray(num_threads, sizeof(iter_t))
|
|
3495
|
+
cdef size_t i
|
|
3496
|
+
|
|
3497
|
+
# For each thread an independent structure.
|
|
3498
|
+
face_iters = [self._face_iter(dual, -2) for _ in range(num_threads)]
|
|
3499
|
+
for i in range(num_threads):
|
|
3500
|
+
face_iter = face_iters[i]
|
|
3501
|
+
structs[i][0] = face_iter.structure[0]
|
|
3502
|
+
|
|
3503
|
+
# Initialize ``f_vector``.
|
|
3504
|
+
cdef size_t *f_vector = <size_t *> mem.calloc((dim + 2), sizeof(size_t))
|
|
3505
|
+
|
|
3506
|
+
parallel_f_vector(structs, num_threads, parallelization_depth, f_vector)
|
|
3507
|
+
|
|
3508
|
+
self._persist_f_vector(f_vector, dual)
|
|
3509
|
+
|
|
3510
|
+
cdef int _persist_f_vector(self, size_t* input_f_vector, bint input_is_reversed) except -1:
|
|
3511
|
+
cdef int dim = self.dimension()
|
|
3512
|
+
|
|
3513
|
+
if input_is_reversed:
|
|
3514
|
+
f_vector = \
|
|
3515
|
+
tuple(smallInteger(input_f_vector[dim + 1 - i]) for i in range(dim + 2))
|
|
3516
|
+
else:
|
|
3517
|
+
f_vector = \
|
|
3518
|
+
tuple(smallInteger(input_f_vector[i]) for i in range(dim + 2))
|
|
3519
|
+
|
|
3520
|
+
# Sanity checks.
|
|
3521
|
+
if dim > 1:
|
|
3522
|
+
if f_vector[-2] < self.n_facets():
|
|
3523
|
+
raise ValueError("not all facets are joins of vertices")
|
|
3524
|
+
if self.is_bounded() and f_vector[1] < self.n_Vrepresentation():
|
|
3525
|
+
raise ValueError("not all vertices are intersections of facets")
|
|
3526
|
+
|
|
3527
|
+
self._f_vector = f_vector
|
|
3528
|
+
|
|
3529
|
+
cdef int _compute_edges_or_ridges(self, int dual, bint do_edges) except -1:
|
|
3530
|
+
r"""
|
|
3531
|
+
Compute the edges of the polyhedron if ``edges`` else the ridges.
|
|
3532
|
+
|
|
3533
|
+
If ``dual``, use the face iterator in dual mode, else in non-dual.
|
|
3534
|
+
If ``dual`` is ``-1`` determine this automatically.
|
|
3535
|
+
|
|
3536
|
+
If the ``f_vector`` is unknown computes it as well if
|
|
3537
|
+
computing the edges in non-dual mode or the ridges in
|
|
3538
|
+
dual-mode.
|
|
3539
|
+
|
|
3540
|
+
See :meth:`CombinatorialPolyhedron.edges` and :meth:`CombinatorialPolyhedron.ridges`.
|
|
3541
|
+
"""
|
|
3542
|
+
if (self._edges is not None and do_edges) or (self._ridges is not None and not do_edges):
|
|
3543
|
+
return 0 # There is no need to recompute.
|
|
3544
|
+
|
|
3545
|
+
if dual == -1:
|
|
3546
|
+
# Determine whether to use dual mode or not.
|
|
3547
|
+
if not self.is_bounded():
|
|
3548
|
+
dual = 0
|
|
3549
|
+
else:
|
|
3550
|
+
algorithm = self.choose_algorithm_to_compute_edges_or_ridges("edges" if do_edges else "ridges")
|
|
3551
|
+
dual = self._algorithm_to_dual(algorithm)
|
|
3552
|
+
|
|
3553
|
+
cdef FaceIterator face_iter
|
|
3554
|
+
cdef int dim = self.dimension()
|
|
3555
|
+
|
|
3556
|
+
cdef ListOfPairs edges = ListOfPairs()
|
|
3557
|
+
cdef int output_dim_init = 1 if do_edges else dim - 2
|
|
3558
|
+
|
|
3559
|
+
cdef size_t* f_vector = NULL
|
|
3560
|
+
|
|
3561
|
+
try:
|
|
3562
|
+
if dim == 1 and (do_edges or self.n_facets() > 1):
|
|
3563
|
+
# In this case there is an edge/ridge, but its not a proper face.
|
|
3564
|
+
edges.add(0, 1)
|
|
3565
|
+
|
|
3566
|
+
elif dim <= 1 or self.n_facets() == 0:
|
|
3567
|
+
# There is no edge/ridge.
|
|
3568
|
+
# Prevent an error when calling the face iterator.
|
|
3569
|
+
pass
|
|
3570
|
+
|
|
3571
|
+
else:
|
|
3572
|
+
if not self._f_vector and ((dual ^ do_edges)):
|
|
3573
|
+
# While doing edges in non-dual mode or ridges in dual-mode
|
|
3574
|
+
# one might as well do the f-vector.
|
|
3575
|
+
f_vector = <size_t *> check_calloc((dim + 2), sizeof(size_t))
|
|
3576
|
+
f_vector[0] = 1
|
|
3577
|
+
f_vector[dim + 1] = 1
|
|
3578
|
+
face_iter = self._face_iter(dual, -2)
|
|
3579
|
+
else:
|
|
3580
|
+
face_iter = self._face_iter(dual, output_dim_init)
|
|
3581
|
+
self._compute_edges_or_ridges_with_iterator(face_iter, (dual ^ do_edges),
|
|
3582
|
+
edges, f_vector)
|
|
3583
|
+
|
|
3584
|
+
# Success, persist the data.
|
|
3585
|
+
if f_vector is not NULL:
|
|
3586
|
+
self._persist_f_vector(f_vector, dual)
|
|
3587
|
+
|
|
3588
|
+
if do_edges:
|
|
3589
|
+
self._edges = edges
|
|
3590
|
+
else:
|
|
3591
|
+
self._ridges = edges
|
|
3592
|
+
finally:
|
|
3593
|
+
sig_free(f_vector)
|
|
3594
|
+
|
|
3595
|
+
if do_edges and self._edges is None:
|
|
3596
|
+
raise ValueError('could not determine edges')
|
|
3597
|
+
elif not do_edges and self._ridges is None:
|
|
3598
|
+
raise ValueError('could not determine ridges')
|
|
3599
|
+
|
|
3600
|
+
def choose_algorithm_to_compute_edges_or_ridges(self, edges_or_ridges):
|
|
3601
|
+
"""
|
|
3602
|
+
Use some heuristics to pick primal or dual algorithm for
|
|
3603
|
+
computation of edges resp. ridges.
|
|
3604
|
+
|
|
3605
|
+
We estimate how long it takes to compute a face using the primal
|
|
3606
|
+
and the dual algorithm. This may differ significantly, so that e.g.
|
|
3607
|
+
visiting all faces with the primal algorithm is faster than using
|
|
3608
|
+
the dual algorithm to just visit vertices and edges.
|
|
3609
|
+
|
|
3610
|
+
We guess the number of edges and ridges and do a wild estimate on
|
|
3611
|
+
the total number of faces.
|
|
3612
|
+
|
|
3613
|
+
INPUT:
|
|
3614
|
+
|
|
3615
|
+
- ``edges_or_ridges`` -- string; one of:
|
|
3616
|
+
* ``'edges'``
|
|
3617
|
+
* ``'ridges'``
|
|
3618
|
+
|
|
3619
|
+
OUTPUT: either ``'primal'`` or ``'dual'``
|
|
3620
|
+
|
|
3621
|
+
EXAMPLES::
|
|
3622
|
+
|
|
3623
|
+
sage: C = polytopes.permutahedron(5).combinatorial_polyhedron()
|
|
3624
|
+
sage: C.choose_algorithm_to_compute_edges_or_ridges("edges")
|
|
3625
|
+
'primal'
|
|
3626
|
+
sage: C.choose_algorithm_to_compute_edges_or_ridges("ridges")
|
|
3627
|
+
'primal'
|
|
3628
|
+
|
|
3629
|
+
::
|
|
3630
|
+
|
|
3631
|
+
sage: C = polytopes.cross_polytope(5).combinatorial_polyhedron()
|
|
3632
|
+
sage: C.choose_algorithm_to_compute_edges_or_ridges("edges")
|
|
3633
|
+
'dual'
|
|
3634
|
+
sage: C.choose_algorithm_to_compute_edges_or_ridges("ridges")
|
|
3635
|
+
'dual'
|
|
3636
|
+
|
|
3637
|
+
|
|
3638
|
+
::
|
|
3639
|
+
|
|
3640
|
+
sage: C = polytopes.Birkhoff_polytope(5).combinatorial_polyhedron()
|
|
3641
|
+
sage: C.choose_algorithm_to_compute_edges_or_ridges("edges")
|
|
3642
|
+
'dual'
|
|
3643
|
+
sage: C.choose_algorithm_to_compute_edges_or_ridges("ridges")
|
|
3644
|
+
'primal'
|
|
3645
|
+
sage: C.choose_algorithm_to_compute_edges_or_ridges("something_else")
|
|
3646
|
+
Traceback (most recent call last):
|
|
3647
|
+
...
|
|
3648
|
+
ValueError: unknown computation goal something_else
|
|
3649
|
+
"""
|
|
3650
|
+
if self.is_simple():
|
|
3651
|
+
per_face_primal = self.n_Vrepresentation() * self.n_facets()
|
|
3652
|
+
else:
|
|
3653
|
+
per_face_primal = self.n_Vrepresentation() * self.n_facets() ** 2
|
|
3654
|
+
|
|
3655
|
+
if self.is_simplicial():
|
|
3656
|
+
per_face_dual = self.n_Vrepresentation() * self.n_facets()
|
|
3657
|
+
else:
|
|
3658
|
+
per_face_dual = self.n_Vrepresentation() ** 2 * self.n_facets()
|
|
3659
|
+
|
|
3660
|
+
from sage.arith.misc import binomial
|
|
3661
|
+
estimate_n_faces = self.dimension() * binomial(min(self.n_facets(), self.n_Vrepresentation()),
|
|
3662
|
+
self.dimension() // 2)
|
|
3663
|
+
|
|
3664
|
+
# Note that the runtime per face already computes the coatoms of the next level, i.e.
|
|
3665
|
+
# the runtime for each facet suffices to compute all ridges in primal,
|
|
3666
|
+
# the runtime for each vertex suffices to compute all edges in dual.
|
|
3667
|
+
if edges_or_ridges == "edges":
|
|
3668
|
+
estimate_primal = estimate_n_faces * per_face_primal
|
|
3669
|
+
estimate_dual = self.n_Vrepresentation() * per_face_dual
|
|
3670
|
+
elif edges_or_ridges == "ridges":
|
|
3671
|
+
estimate_primal = self.n_facets() * per_face_primal
|
|
3672
|
+
estimate_dual = estimate_n_faces * per_face_dual
|
|
3673
|
+
else:
|
|
3674
|
+
raise ValueError(f"unknown computation goal {edges_or_ridges}")
|
|
3675
|
+
|
|
3676
|
+
return 'dual' if (estimate_dual < estimate_primal) else 'primal'
|
|
3677
|
+
|
|
3678
|
+
cdef size_t _compute_edges_or_ridges_with_iterator(
|
|
3679
|
+
self, FaceIterator face_iter, const bint do_atom_rep,
|
|
3680
|
+
ListOfPairs edges, size_t* f_vector) except -1:
|
|
3681
|
+
r"""
|
|
3682
|
+
See :meth:`CombinatorialPolyhedron._compute_edges`.
|
|
3683
|
+
"""
|
|
3684
|
+
cdef size_t a, b # facets of an edge
|
|
3685
|
+
cdef int dim = self.dimension()
|
|
3686
|
+
cdef bint do_f_vector = f_vector is not NULL
|
|
3687
|
+
|
|
3688
|
+
# The dimension in which to record the edges or ridges.
|
|
3689
|
+
cdef output_dimension = 1 if do_atom_rep else dim - 2
|
|
3690
|
+
|
|
3691
|
+
cdef int d = face_iter.next_dimension()
|
|
3692
|
+
while d < dim:
|
|
3693
|
+
sig_check()
|
|
3694
|
+
if do_f_vector:
|
|
3695
|
+
f_vector[d + 1] += 1
|
|
3696
|
+
|
|
3697
|
+
# If ``not do_f_vector`` the iterator is set up
|
|
3698
|
+
# for ``output_dimension`` and
|
|
3699
|
+
# ``d < dim`` implies
|
|
3700
|
+
# ``d == output_dimension``.
|
|
3701
|
+
if not do_f_vector or d == output_dimension:
|
|
3702
|
+
if do_atom_rep:
|
|
3703
|
+
# Set up face_iter.atom_rep
|
|
3704
|
+
face_iter.set_atom_rep()
|
|
3705
|
+
|
|
3706
|
+
# Copy the information.
|
|
3707
|
+
a = face_iter.structure.atom_rep[0]
|
|
3708
|
+
b = face_iter.structure.atom_rep[1]
|
|
3709
|
+
else:
|
|
3710
|
+
# Set up face_iter.coatom_rep
|
|
3711
|
+
face_iter.set_coatom_rep()
|
|
3712
|
+
|
|
3713
|
+
# Copy the information.
|
|
3714
|
+
a = face_iter.structure.coatom_rep[0]
|
|
3715
|
+
b = face_iter.structure.coatom_rep[1]
|
|
3716
|
+
edges.add(a, b)
|
|
3717
|
+
d = face_iter.next_dimension()
|
|
3718
|
+
|
|
3719
|
+
cdef int _compute_face_lattice_incidences(self) except -1:
|
|
3720
|
+
r"""
|
|
3721
|
+
Compute all incidences for the face lattice.
|
|
3722
|
+
|
|
3723
|
+
See :meth:`face_lattice`.
|
|
3724
|
+
"""
|
|
3725
|
+
if self._face_lattice_incidences:
|
|
3726
|
+
return 1 # There is no need to recompute the incidences.
|
|
3727
|
+
|
|
3728
|
+
cdef int dim = self.dimension()
|
|
3729
|
+
f_vector = self.f_vector()
|
|
3730
|
+
self._record_all_faces() # set up ``self._all_faces``
|
|
3731
|
+
cdef PolyhedronFaceLattice all_faces = self._all_faces
|
|
3732
|
+
|
|
3733
|
+
# ``all_faces`` will store its incidences in ``first`` and ``second``.
|
|
3734
|
+
cdef size_t first = 0, second = 0
|
|
3735
|
+
|
|
3736
|
+
# ``dimension_one`` and ``dimension_two`` will be the dimensions of the
|
|
3737
|
+
# incidences, we currently obtain from ``all_faces``.
|
|
3738
|
+
# Almost always ``dimension_two = dimension_one - 1``.
|
|
3739
|
+
cdef int dimension_one, dimension_two
|
|
3740
|
+
cdef int j # an index for ``range(dimension_two + 1)``
|
|
3741
|
+
|
|
3742
|
+
# The indices of the incidences in ``all_faces`` are levelwise.
|
|
3743
|
+
# Hence, we have to add to each index dependent on dimension:
|
|
3744
|
+
|
|
3745
|
+
# For ``dimension_two`` we add:
|
|
3746
|
+
cdef size_t already_seen # = sum(f_vector[j] for j in range(dimension_two + 1))
|
|
3747
|
+
|
|
3748
|
+
# For ``dimension_one`` we add:
|
|
3749
|
+
cdef size_t already_seen_next # = sum(f_vector[j] for j in range(dimension_two + 2))
|
|
3750
|
+
|
|
3751
|
+
cdef ListOfPairs incidences = ListOfPairs()
|
|
3752
|
+
|
|
3753
|
+
if all_faces is None:
|
|
3754
|
+
raise ValueError("could not determine a list of all faces")
|
|
3755
|
+
|
|
3756
|
+
dimension_one = 0
|
|
3757
|
+
if dim > -1:
|
|
3758
|
+
while f_vector[dimension_one + 1] == 0:
|
|
3759
|
+
# Taking care of cases, where there might be no faces
|
|
3760
|
+
# of dimension 0, 1, etc (``n_lines > 0``).
|
|
3761
|
+
dimension_one += 1
|
|
3762
|
+
dimension_two = -1
|
|
3763
|
+
|
|
3764
|
+
while dimension_one < dim + 1:
|
|
3765
|
+
already_seen = sum(f_vector[j] for j in range(dimension_two + 1))
|
|
3766
|
+
already_seen_next = already_seen + f_vector[dimension_two + 1]
|
|
3767
|
+
|
|
3768
|
+
if all_faces.dual:
|
|
3769
|
+
# If ``dual``, then ``all_faces`` has the dimensions reversed.
|
|
3770
|
+
all_faces.incidence_init(dim - 1 - dimension_two, dim - 1 - dimension_one)
|
|
3771
|
+
else:
|
|
3772
|
+
all_faces.incidence_init(dimension_one, dimension_two)
|
|
3773
|
+
|
|
3774
|
+
# Get all incidences for fixed ``[dimension_one, dimension_two]``.
|
|
3775
|
+
while all_faces.next_incidence(&second, &first):
|
|
3776
|
+
if all_faces.dual:
|
|
3777
|
+
# If ``dual``, then ``second`` and ``first are flipped.
|
|
3778
|
+
second += already_seen
|
|
3779
|
+
first += already_seen_next
|
|
3780
|
+
incidences.add(second, first)
|
|
3781
|
+
else:
|
|
3782
|
+
second += already_seen_next
|
|
3783
|
+
first += already_seen
|
|
3784
|
+
incidences.add(first, second)
|
|
3785
|
+
|
|
3786
|
+
sig_check()
|
|
3787
|
+
|
|
3788
|
+
# Increase dimensions.
|
|
3789
|
+
dimension_one += 1
|
|
3790
|
+
dimension_two = dimension_one - 1
|
|
3791
|
+
|
|
3792
|
+
# Success, persist the data.
|
|
3793
|
+
self._face_lattice_incidences = incidences
|
|
3794
|
+
|
|
3795
|
+
def _record_all_faces(self):
|
|
3796
|
+
r"""
|
|
3797
|
+
Initialize :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.polyhedron_faces_lattice.PolyhedronFaceLattice` for the polyhedron.
|
|
3798
|
+
|
|
3799
|
+
Record and sort all faces of the polyhedron in that class.
|
|
3800
|
+
|
|
3801
|
+
EXAMPLES::
|
|
3802
|
+
|
|
3803
|
+
sage: P = polytopes.cyclic_polytope(4,10)
|
|
3804
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
3805
|
+
sage: C._record_all_faces()
|
|
3806
|
+
|
|
3807
|
+
TESTS::
|
|
3808
|
+
|
|
3809
|
+
sage: # needs sage.combinat
|
|
3810
|
+
sage: P = polytopes.permutahedron(4)
|
|
3811
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
3812
|
+
sage: it = C.face_generator()
|
|
3813
|
+
sage: tup = tuple((face.ambient_Vrepresentation(),
|
|
3814
|
+
....: face.ambient_Hrepresentation()) for face in it)
|
|
3815
|
+
sage: rg = range(1,sum(C.f_vector()) - 1)
|
|
3816
|
+
sage: tup2 = tuple(
|
|
3817
|
+
....: (C.face_by_face_lattice_index(i).ambient_Vrepresentation(),
|
|
3818
|
+
....: C.face_by_face_lattice_index(i).ambient_Hrepresentation())
|
|
3819
|
+
....: for i in rg)
|
|
3820
|
+
sage: sorted(tup) == sorted(tup2)
|
|
3821
|
+
True
|
|
3822
|
+
|
|
3823
|
+
sage: P = polytopes.cyclic_polytope(4,10)
|
|
3824
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
3825
|
+
sage: it = C.face_generator()
|
|
3826
|
+
sage: tup = tuple((face.ambient_Vrepresentation(),face.ambient_Hrepresentation()) for face in it)
|
|
3827
|
+
sage: rg = range(1,sum(C.f_vector()) - 1)
|
|
3828
|
+
sage: tup2 = tuple((C.face_by_face_lattice_index(i).ambient_Vrepresentation(),
|
|
3829
|
+
....: C.face_by_face_lattice_index(i).ambient_Hrepresentation()) for i in rg)
|
|
3830
|
+
sage: sorted(tup) == sorted(tup2)
|
|
3831
|
+
True
|
|
3832
|
+
|
|
3833
|
+
sage: P = Polyhedron(rays=[[1,0,0], [-1,0,0], [0,-1,0]])
|
|
3834
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
3835
|
+
sage: it = C.face_generator()
|
|
3836
|
+
sage: tup = tuple((face.ambient_Vrepresentation(),face.ambient_Hrepresentation()) for face in it)
|
|
3837
|
+
sage: rg = range(1,sum(C.f_vector()) - 1)
|
|
3838
|
+
sage: tup2 = tuple((C.face_by_face_lattice_index(i).ambient_Vrepresentation(),
|
|
3839
|
+
....: C.face_by_face_lattice_index(i).ambient_Hrepresentation()) for i in rg)
|
|
3840
|
+
sage: sorted(tup) == sorted(tup2)
|
|
3841
|
+
True
|
|
3842
|
+
|
|
3843
|
+
sage: P = Polyhedron(rays=[[1,0,0], [-1,0,0],
|
|
3844
|
+
....: [0,-1,0], [0,1,0]])
|
|
3845
|
+
sage: C = CombinatorialPolyhedron(P)
|
|
3846
|
+
sage: it = C.face_generator()
|
|
3847
|
+
sage: tup = tuple((face.ambient_Vrepresentation(),face.ambient_Hrepresentation()) for face in it)
|
|
3848
|
+
sage: rg = range(1,sum(C.f_vector()) - 1)
|
|
3849
|
+
sage: tup2 = tuple((C.face_by_face_lattice_index(i).ambient_Vrepresentation(),
|
|
3850
|
+
....: C.face_by_face_lattice_index(i).ambient_Hrepresentation()) for i in rg)
|
|
3851
|
+
sage: sorted(tup) == sorted(tup2)
|
|
3852
|
+
True
|
|
3853
|
+
"""
|
|
3854
|
+
if self._all_faces:
|
|
3855
|
+
return # Have recorded all faces already.
|
|
3856
|
+
|
|
3857
|
+
self._all_faces = PolyhedronFaceLattice(self)
|
|
3858
|
+
if self._all_faces is None:
|
|
3859
|
+
raise RuntimeError("could not determine a list of all faces")
|