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

Commit 5764ce4

Browse files
author
Jonathan Kliem
committed
implemented is_pyramid, is_bipyramid, is_prism
1 parent 7d561d8 commit 5764ce4

File tree

2 files changed

+306
-0
lines changed

2 files changed

+306
-0
lines changed

src/doc/en/thematic_tutorials/geometry/polyhedra_quickref.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ List of Polyhedron methods
5555
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.is_simplicial` | checks whether all faces of the polyhedron are simplices
5656
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.is_lawrence_polytope` | tests whether self is a Lawrence polytope
5757
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.is_self_dual` | tests whether the polytope is self-dual
58+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.is_pyramid` | test whether the polytope is a pyramid over one of its facets
59+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.is_bipyramid` | test whether the polytope is combinatorially equivalent to a bipyramid over some polytope
60+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.is_prism` | test whether the polytope is combinatorially equivalent to a prism of some polytope
5861

5962
**Enumerative properties**
6063

src/sage/geometry/polyhedron/base.py

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,6 +2685,309 @@ def is_simplicial(self):
26852685
for facet in self.Hrepresentation()
26862686
if not facet.is_equation())
26872687

2688+
def is_pyramid(self, certificate=False):
2689+
"""
2690+
Test whether the polytope is a pyramid over one of its facets.
2691+
2692+
INPUT:
2693+
2694+
- ``certificate`` -- boolean (default: ``False``); specifies whether
2695+
to return a vertex of the polytope which is the apex of a pyramid,
2696+
if found
2697+
2698+
OUTPUT:
2699+
2700+
If ``certificate`` is ``True``, returns a tuple containing:
2701+
2702+
1. Boolean.
2703+
2. The apex of the pyramid or ``None``.
2704+
2705+
If ``certificate`` is ``False`` returns a boolean.
2706+
2707+
EXAMPLES::
2708+
2709+
sage: P = polytopes.simplex(3)
2710+
sage: P.is_pyramid()
2711+
True
2712+
sage: P.is_pyramid(certificate=True)
2713+
(True, A vertex at (0, 0, 0, 1))
2714+
sage: egyptian_pyramid = polytopes.regular_polygon(4).pyramid()
2715+
sage: egyptian_pyramid.is_pyramid()
2716+
True
2717+
sage: Q = polytopes.octahedron()
2718+
sage: Q.is_pyramid()
2719+
False
2720+
"""
2721+
if not self.is_compact():
2722+
raise ValueError("polyhedron has to be compact")
2723+
2724+
# Find a vertex that is incident to all elements in Hrepresentation but one.
2725+
IM = self.incidence_matrix()
2726+
for index in range(self.n_vertices()):
2727+
vertex_incidences = IM.row(index)
2728+
if sum(vertex_incidences) == IM.ncols() - 1:
2729+
if certificate:
2730+
return (True, self.vertices()[index])
2731+
return True
2732+
if certificate:
2733+
return (False, None)
2734+
return False
2735+
2736+
def is_bipyramid(self, certificate=False):
2737+
r"""
2738+
Test whether the polytope is combinatorially equivalent to a
2739+
bipyramid over some polytope.
2740+
2741+
INPUT:
2742+
2743+
- ``certificate`` -- boolean (default: ``False``); specifies whether
2744+
to return two vertices of the polytope which are the apices of a
2745+
bipyramid, if found
2746+
2747+
OUTPUT:
2748+
2749+
If ``certificate`` is ``True``, returns a tuple containing:
2750+
2751+
1. Boolean.
2752+
2. ``None`` or a tuple containing:
2753+
a. The first apex.
2754+
b. The second apex.
2755+
2756+
If ``certificate`` is ``False`` returns a boolean.
2757+
2758+
EXAMPLES::
2759+
2760+
sage: P = polytopes.octahedron()
2761+
sage: P.is_bipyramid()
2762+
True
2763+
sage: P.is_bipyramid(certificate=True)
2764+
(True, [A vertex at (-1, 0, 0), A vertex at (1, 0, 0)])
2765+
sage: Q = polytopes.cyclic_polytope(3,7)
2766+
sage: Q.is_bipyramid()
2767+
False
2768+
sage: R = Q.bipyramid()
2769+
sage: R.is_bipyramid(certificate=True)
2770+
(True, [A vertex at (-1, 3, 13, 63), A vertex at (1, 3, 13, 63)])
2771+
2772+
TESTS::
2773+
2774+
sage: P = polytopes.permutahedron(4).bipyramid()
2775+
sage: P.is_bipyramid()
2776+
True
2777+
2778+
sage: P = polytopes.cube()
2779+
sage: P.is_bipyramid()
2780+
False
2781+
2782+
sage: P = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,1]])
2783+
sage: P.is_bipyramid()
2784+
Traceback (most recent call last):
2785+
...
2786+
ValueError: polyhedron has to be compact
2787+
2788+
ALGORITHM:
2789+
2790+
Assume all faces of a polyhedron to be given as lists of vertices.
2791+
2792+
A polytope is a bipyramid with apexes `v`, `w` if and only if for each
2793+
proper face `v \in F` there exists a face `G` with
2794+
`G \setminus \{w\} = F \setminus \{v\}`
2795+
and vice versa (for each proper face
2796+
`w \in F` there exists ...).
2797+
2798+
To check this property it suffices to check for all facets of the polyhedron.
2799+
"""
2800+
if not self.is_compact():
2801+
raise ValueError("polyhedron has to be compact")
2802+
2803+
from sage.misc.functional import is_odd
2804+
n_verts = self.n_vertices()
2805+
n_facets = self.n_facets()
2806+
if is_odd(n_facets):
2807+
if certificate:
2808+
return (False, None)
2809+
return False
2810+
2811+
IM = self.incidence_matrix()
2812+
if self.n_equations():
2813+
# Remove equations from the incidence matrix,
2814+
# such that this is the vertex-facet incidences matrix.
2815+
I1 = IM.transpose()
2816+
I2 = I1[[i for i in range(self.n_Hrepresentation())
2817+
if not self.Hrepresentation()[i].is_equation()]]
2818+
IM = I2.transpose()
2819+
2820+
facets_incidences = [set(column.nonzero_positions()) for column in IM.columns()]
2821+
verts_incidences = dict()
2822+
for i in range(n_verts):
2823+
v_i = set(IM.row(i).nonzero_positions())
2824+
if len(v_i) == n_facets/2:
2825+
verts_incidences[i] = v_i
2826+
2827+
# Find two vertices ``vert1`` and ``vert2`` such that one of them
2828+
# lies on exactly half of the facets, and the other one lies on
2829+
# exactly the other half.
2830+
from itertools import combinations
2831+
for index1, index2 in combinations(verts_incidences, 2):
2832+
vert1_incidences = verts_incidences[index1]
2833+
vert2_incidences = verts_incidences[index2]
2834+
vert1and2 = vert1_incidences.union(vert2_incidences)
2835+
if len(vert1and2) == n_facets:
2836+
# We have found two candidates for apexes.
2837+
# Remove from each facet ``index1`` resp. ``index2``.
2838+
test_facets = set(frozenset(facet_inc.difference({index1, index2}))
2839+
for facet_inc in facets_incidences)
2840+
if len(test_facets) == n_facets/2:
2841+
# For each `F` containing `index1` there is
2842+
# `G` containing `index2` such that
2843+
# `F \setminus \{index1\} = G \setminus \{index2\}
2844+
# and vice versa.
2845+
if certificate:
2846+
V = self.vertices()
2847+
return (True, [V[index1], V[index2]])
2848+
return True
2849+
2850+
if certificate:
2851+
return (False, None)
2852+
return False
2853+
2854+
def is_prism(self, certificate=False):
2855+
"""
2856+
Test whether the polytope is combinatorially equivalent to a prism of
2857+
some polytope.
2858+
2859+
INPUT:
2860+
2861+
- ``certificate`` -- boolean (default: ``False``); specifies whether
2862+
to return two facets of the polytope which are the bases of a prism,
2863+
if found
2864+
2865+
OUTPUT:
2866+
2867+
If ``certificate`` is ``True``, returns a tuple containing:
2868+
2869+
1. Boolean.
2870+
2. ``None`` or a tuple containing:
2871+
a. List of the vertices of the first base facet.
2872+
b. List of the vertices of the second base facet.
2873+
2874+
If ``certificate`` is ``False`` returns a boolean.
2875+
2876+
EXAMPLES::
2877+
2878+
sage: P = polytopes.cube()
2879+
sage: P.is_prism()
2880+
True
2881+
sage: P.is_prism(certificate=True)
2882+
(True,
2883+
[[A vertex at (-1, -1, 1),
2884+
A vertex at (-1, 1, 1),
2885+
A vertex at (1, -1, 1),
2886+
A vertex at (1, 1, 1)],
2887+
[A vertex at (-1, -1, -1),
2888+
A vertex at (-1, 1, -1),
2889+
A vertex at (1, -1, -1),
2890+
A vertex at (1, 1, -1)]])
2891+
sage: Q = polytopes.cyclic_polytope(3,8)
2892+
sage: Q.is_prism()
2893+
False
2894+
sage: R = Q.prism()
2895+
sage: R.is_prism(certificate=True)
2896+
(True,
2897+
[[A vertex at (1, 0, 0, 0),
2898+
A vertex at (1, 1, 1, 1),
2899+
A vertex at (1, 2, 4, 8),
2900+
A vertex at (1, 3, 9, 27),
2901+
A vertex at (1, 4, 16, 64),
2902+
A vertex at (1, 5, 25, 125),
2903+
A vertex at (1, 6, 36, 216),
2904+
A vertex at (1, 7, 49, 343)],
2905+
[A vertex at (0, 0, 0, 0),
2906+
A vertex at (0, 1, 1, 1),
2907+
A vertex at (0, 2, 4, 8),
2908+
A vertex at (0, 3, 9, 27),
2909+
A vertex at (0, 4, 16, 64),
2910+
A vertex at (0, 5, 25, 125),
2911+
A vertex at (0, 6, 36, 216),
2912+
A vertex at (0, 7, 49, 343)]])
2913+
2914+
TESTS::
2915+
2916+
sage: P = polytopes.cross_polytope(5)
2917+
sage: P.is_prism()
2918+
False
2919+
2920+
sage: P = polytopes.permutahedron(4).prism()
2921+
sage: P.is_prism()
2922+
True
2923+
2924+
sage: P = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,1]])
2925+
sage: P.is_prism()
2926+
Traceback (most recent call last):
2927+
...
2928+
NotImplementedError: polyhedron has to be compact
2929+
2930+
ALGORITHM:
2931+
2932+
See :meth:`Polyhedron_base.is_bipyramid`.
2933+
"""
2934+
if not self.is_compact():
2935+
raise NotImplementedError("polyhedron has to be compact")
2936+
2937+
from sage.misc.functional import is_odd
2938+
n_verts = self.n_vertices()
2939+
n_facets = self.n_facets()
2940+
if is_odd(n_verts):
2941+
if certificate:
2942+
return (False, None)
2943+
return False
2944+
2945+
IM = self.incidence_matrix()
2946+
if self.n_equations():
2947+
# Remove equations from the incidence matrix,
2948+
# such that this is the vertex-facet incidences matrix.
2949+
I1 = IM.transpose()
2950+
I2 = I1[[i for i in range(self.n_Hrepresentation())
2951+
if not self.Hrepresentation()[i].is_equation()]]
2952+
IM = I2.transpose()
2953+
2954+
verts_incidences = [set(row.nonzero_positions()) for row in IM.rows()]
2955+
facets_incidences = dict()
2956+
for j in range(n_facets):
2957+
F_j = set(IM.column(j).nonzero_positions())
2958+
if len(F_j) == n_verts/2:
2959+
facets_incidences[j] = F_j
2960+
2961+
# Find two vertices ``facet1`` and ``facet2`` such that one of them
2962+
# contains exactly half of the vertices, and the other one contains
2963+
# exactly the other half.
2964+
from itertools import combinations
2965+
for index1, index2 in combinations(facets_incidences, 2):
2966+
facet1_incidences = facets_incidences[index1]
2967+
facet2_incidences = facets_incidences[index2]
2968+
facet1and2 = facet1_incidences.union(facet2_incidences)
2969+
if len(facet1and2) == n_verts:
2970+
# We have found two candidates for base faces.
2971+
# Remove from each vertex ``index1`` resp. ``index2``.
2972+
test_verts = set(frozenset(vert_inc.difference({index1, index2}))
2973+
for vert_inc in verts_incidences)
2974+
if len(test_verts) == n_verts/2:
2975+
# For each vertex containing `index1` there is
2976+
# another one contained in `index2`
2977+
# and vice versa.
2978+
# Other than `index1` and `index2` both are contained in
2979+
# exactly the same facets.
2980+
if certificate:
2981+
V = self.vertices()
2982+
facet1_vertices = [V[i] for i in facet1_incidences]
2983+
facet2_vertices = [V[i] for i in facet2_incidences]
2984+
return (True, [facet1_vertices, facet2_vertices])
2985+
return True
2986+
2987+
if certificate:
2988+
return (False, None)
2989+
return False
2990+
26882991
def hyperplane_arrangement(self):
26892992
"""
26902993
Return the hyperplane arrangement defined by the equations and

0 commit comments

Comments
 (0)