@@ -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