passagemath-polyhedra 10.6.37__cp314-cp314-musllinux_1_2_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.
- passagemath_polyhedra/__init__.py +3 -0
- passagemath_polyhedra-10.6.37.dist-info/METADATA +367 -0
- passagemath_polyhedra-10.6.37.dist-info/METADATA.bak +369 -0
- passagemath_polyhedra-10.6.37.dist-info/RECORD +209 -0
- passagemath_polyhedra-10.6.37.dist-info/WHEEL +5 -0
- passagemath_polyhedra-10.6.37.dist-info/top_level.txt +3 -0
- passagemath_polyhedra.libs/libgcc_s-0cd532bd.so.1 +0 -0
- passagemath_polyhedra.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
- passagemath_polyhedra.libs/libgomp-8949ffbe.so.1.0.0 +0 -0
- passagemath_polyhedra.libs/libstdc++-5d72f927.so.6.0.33 +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 +3905 -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-musl.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-musl.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-musl.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-musl.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-musl.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-musl.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-musl.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-musl.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-musl.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-musl.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-musl.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-musl.so +0 -0
- sage/numerical/backends/cvxopt_sdp_backend.pyx +382 -0
- sage/numerical/backends/cvxpy_backend.cpython-314-x86_64-linux-musl.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-musl.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-musl.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-musl.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-musl.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-musl.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-musl.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-musl.so +0 -0
- sage/numerical/mip.pxd +40 -0
- sage/numerical/mip.pyx +3667 -0
- sage/numerical/sdp.cpython-314-x86_64-linux-musl.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-musl.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,2700 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-polyhedra
|
|
2
|
+
r"""
|
|
3
|
+
Base class for polyhedra: Methods for constructing new polyhedra
|
|
4
|
+
|
|
5
|
+
Except for affine hull and affine hull projection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# ****************************************************************************
|
|
9
|
+
# Copyright (C) 2008-2012 Marshall Hampton <hamptonio@gmail.com>
|
|
10
|
+
# Copyright (C) 2011-2015 Volker Braun <vbraun.name@gmail.com>
|
|
11
|
+
# Copyright (C) 2012-2018 Frederic Chapoton
|
|
12
|
+
# Copyright (C) 2013 Andrey Novoseltsev
|
|
13
|
+
# Copyright (C) 2014-2017 Moritz Firsching
|
|
14
|
+
# Copyright (C) 2014-2019 Thierry Monteil
|
|
15
|
+
# Copyright (C) 2015 Nathann Cohen
|
|
16
|
+
# Copyright (C) 2015-2017 Jeroen Demeyer
|
|
17
|
+
# Copyright (C) 2015-2017 Vincent Delecroix
|
|
18
|
+
# Copyright (C) 2015-2018 Dima Pasechnik
|
|
19
|
+
# Copyright (C) 2015-2020 Jean-Philippe Labbe <labbe at math.huji.ac.il>
|
|
20
|
+
# Copyright (C) 2015-2021 Matthias Koeppe
|
|
21
|
+
# Copyright (C) 2016-2019 Daniel Krenn
|
|
22
|
+
# Copyright (C) 2017 Marcelo Forets
|
|
23
|
+
# Copyright (C) 2017-2018 Mark Bell
|
|
24
|
+
# Copyright (C) 2019 Julian Ritter
|
|
25
|
+
# Copyright (C) 2019-2020 Laith Rastanawi
|
|
26
|
+
# Copyright (C) 2019-2020 Sophia Elia
|
|
27
|
+
# Copyright (C) 2019-2021 Jonathan Kliem <jonathan.kliem@gmail.com>
|
|
28
|
+
#
|
|
29
|
+
# This program is free software: you can redistribute it and/or modify
|
|
30
|
+
# it under the terms of the GNU General Public License as published by
|
|
31
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
32
|
+
# (at your option) any later version.
|
|
33
|
+
# https://www.gnu.org/licenses/
|
|
34
|
+
# ****************************************************************************
|
|
35
|
+
|
|
36
|
+
from sage.features import FeatureNotPresentError
|
|
37
|
+
from sage.structure.element import coerce_binop, Vector, Matrix
|
|
38
|
+
|
|
39
|
+
from sage.rings.integer_ring import ZZ
|
|
40
|
+
from sage.rings.rational_field import QQ
|
|
41
|
+
from sage.matrix.constructor import matrix
|
|
42
|
+
|
|
43
|
+
from sage.modules.free_module_element import vector
|
|
44
|
+
|
|
45
|
+
from .base4 import Polyhedron_base4
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Polyhedron_base5(Polyhedron_base4):
|
|
49
|
+
"""
|
|
50
|
+
Methods constructing new polyhedra
|
|
51
|
+
except for affine hull and affine hull projection.
|
|
52
|
+
|
|
53
|
+
See :class:`sage.geometry.polyhedron.base.Polyhedron_base`.
|
|
54
|
+
|
|
55
|
+
TESTS::
|
|
56
|
+
|
|
57
|
+
sage: from sage.geometry.polyhedron.base5 import Polyhedron_base5
|
|
58
|
+
sage: P = polytopes.cube()
|
|
59
|
+
sage: Q = polytopes.cross_polytope(3)
|
|
60
|
+
|
|
61
|
+
Unary operations::
|
|
62
|
+
|
|
63
|
+
sage: Polyhedron_base5.__neg__(P)
|
|
64
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
|
|
65
|
+
sage: Polyhedron_base5.polar(P)
|
|
66
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 6 vertices
|
|
67
|
+
sage: Polyhedron_base5.pyramid(P)
|
|
68
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 9 vertices
|
|
69
|
+
sage: Polyhedron_base5.bipyramid(P)
|
|
70
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 10 vertices
|
|
71
|
+
sage: Polyhedron_base5.prism(P)
|
|
72
|
+
A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 16 vertices
|
|
73
|
+
sage: Polyhedron_base5.truncation(P)
|
|
74
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 24 vertices
|
|
75
|
+
sage: Polyhedron_base5.lawrence_polytope(P)
|
|
76
|
+
A 11-dimensional polyhedron in ZZ^11 defined as the convex hull of 16 vertices
|
|
77
|
+
|
|
78
|
+
Binary operations::
|
|
79
|
+
|
|
80
|
+
sage: Polyhedron_base5.minkowski_sum(P, Q)
|
|
81
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 24 vertices
|
|
82
|
+
sage: Polyhedron_base5.minkowski_difference(P, Q)
|
|
83
|
+
A 0-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex
|
|
84
|
+
sage: Polyhedron_base5.product(P, Q)
|
|
85
|
+
A 6-dimensional polyhedron in ZZ^6 defined as the convex hull of 48 vertices
|
|
86
|
+
sage: Polyhedron_base5.join(P, Q)
|
|
87
|
+
A 7-dimensional polyhedron in ZZ^7 defined as the convex hull of 14 vertices
|
|
88
|
+
sage: Polyhedron_base5.subdirect_sum(P, Q)
|
|
89
|
+
A 6-dimensional polyhedron in ZZ^6 defined as the convex hull of 14 vertices
|
|
90
|
+
sage: Polyhedron_base5.direct_sum(P, Q)
|
|
91
|
+
A 6-dimensional polyhedron in QQ^6 defined as the convex hull of 14 vertices
|
|
92
|
+
sage: Polyhedron_base5.convex_hull(P, Q)
|
|
93
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
|
|
94
|
+
sage: Polyhedron_base5.intersection(P, Q)
|
|
95
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 6 vertices
|
|
96
|
+
|
|
97
|
+
Actions::
|
|
98
|
+
|
|
99
|
+
sage: Polyhedron_base5.translation(P, vector([1, 1, 1]))
|
|
100
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
|
|
101
|
+
sage: Polyhedron_base5.dilation(P, 2)
|
|
102
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
|
|
103
|
+
sage: Polyhedron_base5.__truediv__(P, 2)
|
|
104
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 8 vertices
|
|
105
|
+
sage: Polyhedron_base5.linear_transformation(P, matrix([[2, 1, 2], [1, 2, 4], [3, 1, 2]]))
|
|
106
|
+
A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices
|
|
107
|
+
|
|
108
|
+
Methods using a face::
|
|
109
|
+
|
|
110
|
+
sage: Polyhedron_base5.face_truncation(P, P.facets()[0])
|
|
111
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 8 vertices
|
|
112
|
+
sage: Polyhedron_base5.stack(P, P.facets()[0])
|
|
113
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 9 vertices
|
|
114
|
+
sage: Polyhedron_base5.wedge(P, P.facets()[0])
|
|
115
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 12 vertices
|
|
116
|
+
sage: Polyhedron_base5.face_split(P, P.faces(2)[0])
|
|
117
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 10 vertices
|
|
118
|
+
|
|
119
|
+
Methods using a vertex or vector::
|
|
120
|
+
|
|
121
|
+
sage: Polyhedron_base5.lawrence_extension(P, P.vertices()[0])
|
|
122
|
+
A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 9 vertices
|
|
123
|
+
sage: Polyhedron_base5.one_point_suspension(P, P.vertices()[0])
|
|
124
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 9 vertices
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
###########################################################
|
|
128
|
+
# Unary operations.
|
|
129
|
+
###########################################################
|
|
130
|
+
|
|
131
|
+
def __neg__(self):
|
|
132
|
+
"""
|
|
133
|
+
Negation of a polytope is defined as inverting the coordinates.
|
|
134
|
+
|
|
135
|
+
EXAMPLES::
|
|
136
|
+
|
|
137
|
+
sage: t = polytopes.simplex(3,project=False); t.vertices()
|
|
138
|
+
(A vertex at (0, 0, 0, 1), A vertex at (0, 0, 1, 0),
|
|
139
|
+
A vertex at (0, 1, 0, 0), A vertex at (1, 0, 0, 0))
|
|
140
|
+
sage: neg_ = -t
|
|
141
|
+
sage: neg_.vertices()
|
|
142
|
+
(A vertex at (-1, 0, 0, 0), A vertex at (0, -1, 0, 0),
|
|
143
|
+
A vertex at (0, 0, -1, 0), A vertex at (0, 0, 0, -1))
|
|
144
|
+
|
|
145
|
+
TESTS::
|
|
146
|
+
|
|
147
|
+
sage: p = Polyhedron(ieqs=[[1,1,0]])
|
|
148
|
+
sage: p.rays()
|
|
149
|
+
(A ray in the direction (1, 0),)
|
|
150
|
+
sage: pneg = p.__neg__()
|
|
151
|
+
sage: pneg.rays()
|
|
152
|
+
(A ray in the direction (-1, 0),)
|
|
153
|
+
"""
|
|
154
|
+
return self.dilation(-1)
|
|
155
|
+
|
|
156
|
+
def polar(self, in_affine_span=False):
|
|
157
|
+
"""
|
|
158
|
+
Return the polar (dual) polytope.
|
|
159
|
+
|
|
160
|
+
The original vertices are translated so that their barycenter
|
|
161
|
+
is at the origin, and then the vertices are used as the
|
|
162
|
+
coefficients in the polar inequalities.
|
|
163
|
+
|
|
164
|
+
The polytope must be full-dimensional, unless ``in_affine_span`` is ``True``.
|
|
165
|
+
If ``in_affine_span`` is ``True``, then the operation will be performed in the
|
|
166
|
+
linear/affine span of the polyhedron (after translation).
|
|
167
|
+
|
|
168
|
+
EXAMPLES::
|
|
169
|
+
|
|
170
|
+
sage: p = Polyhedron(vertices=[[0,0,1], [0,1,0], [1,0,0], [0,0,0], [1,1,1]],
|
|
171
|
+
....: base_ring=QQ); p
|
|
172
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 5 vertices
|
|
173
|
+
sage: p.polar()
|
|
174
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 6 vertices
|
|
175
|
+
|
|
176
|
+
sage: cube = polytopes.hypercube(3)
|
|
177
|
+
sage: octahedron = polytopes.cross_polytope(3)
|
|
178
|
+
sage: cube_dual = cube.polar()
|
|
179
|
+
sage: octahedron == cube_dual
|
|
180
|
+
True
|
|
181
|
+
|
|
182
|
+
``in_affine_span`` somewhat ignores equations, performing the polar in the
|
|
183
|
+
spanned subspace (after translating barycenter to origin)::
|
|
184
|
+
|
|
185
|
+
sage: P = polytopes.simplex(3, base_ring=QQ)
|
|
186
|
+
sage: P.polar(in_affine_span=True)
|
|
187
|
+
A 3-dimensional polyhedron in QQ^4 defined as the convex hull of 4 vertices
|
|
188
|
+
|
|
189
|
+
Embedding the polytope in a higher dimension, commutes with polar in this case::
|
|
190
|
+
|
|
191
|
+
sage: point = Polyhedron([[0]])
|
|
192
|
+
sage: P = polytopes.cube().change_ring(QQ)
|
|
193
|
+
sage: (P*point).polar(in_affine_span=True) == P.polar()*point
|
|
194
|
+
True
|
|
195
|
+
|
|
196
|
+
TESTS:
|
|
197
|
+
|
|
198
|
+
Check that :issue:`25081` is fixed::
|
|
199
|
+
|
|
200
|
+
sage: # needs cddexec_gmp
|
|
201
|
+
sage: C = polytopes.hypercube(4, backend='cdd')
|
|
202
|
+
sage: C.polar().backend()
|
|
203
|
+
'cdd'
|
|
204
|
+
|
|
205
|
+
Check that :issue:`28850` is fixed::
|
|
206
|
+
|
|
207
|
+
sage: P = polytopes.simplex(3, base_ring=QQ)
|
|
208
|
+
sage: P.polar()
|
|
209
|
+
Traceback (most recent call last):
|
|
210
|
+
...
|
|
211
|
+
ValueError: not full-dimensional; try with 'in_affine_span=True'
|
|
212
|
+
|
|
213
|
+
Check that the double description is set up correctly::
|
|
214
|
+
|
|
215
|
+
sage: P = Polyhedron([[1,0],[0,1],[-1,-1]], backend='field')
|
|
216
|
+
sage: Q = P.change_ring(QQ, backend='ppl')
|
|
217
|
+
sage: P.polar() == Q.polar()
|
|
218
|
+
True
|
|
219
|
+
|
|
220
|
+
sage: P = polytopes.simplex(4, backend='field')
|
|
221
|
+
sage: Q = P.change_ring(QQ, backend='ppl')
|
|
222
|
+
sage: P.polar(in_affine_span=True) == Q.polar(in_affine_span=True)
|
|
223
|
+
True
|
|
224
|
+
|
|
225
|
+
Check that it works, even when the equations are not orthogonal to each other::
|
|
226
|
+
|
|
227
|
+
sage: P = polytopes.cube()*Polyhedron([[0,0,0]])
|
|
228
|
+
sage: P = P.change_ring(QQ)
|
|
229
|
+
|
|
230
|
+
sage: from sage.geometry.polyhedron.backend_field import Polyhedron_field
|
|
231
|
+
sage: from sage.geometry.polyhedron.parent import Polyhedra_field
|
|
232
|
+
sage: parent = Polyhedra_field(QQ, 6, 'field')
|
|
233
|
+
sage: equations = [[0, 0, 0, 0, 1, 1, 1], [0, 0, 0, 0, -1, 1, -1], [0, 0, 0, 0, 1, -1, -1]]
|
|
234
|
+
sage: Q = Polyhedron_field(parent, [P.vertices(), [], []], [P.inequalities(), equations],
|
|
235
|
+
....: Vrep_minimal=True, Hrep_minimal=True)
|
|
236
|
+
sage: Q == P
|
|
237
|
+
True
|
|
238
|
+
sage: Q.polar(in_affine_span=True) == P.polar(in_affine_span=True)
|
|
239
|
+
True
|
|
240
|
+
"""
|
|
241
|
+
if not self.is_compact():
|
|
242
|
+
raise ValueError("not a polytope")
|
|
243
|
+
if not in_affine_span and not self.dim() == self.ambient_dim():
|
|
244
|
+
raise ValueError("not full-dimensional; try with 'in_affine_span=True'")
|
|
245
|
+
|
|
246
|
+
t_Vrep, t_Hrep, parent = self._translation_double_description(-self.center())
|
|
247
|
+
t_verts = t_Vrep[0]
|
|
248
|
+
t_ieqs = t_Hrep[0]
|
|
249
|
+
t_eqns = t_Hrep[1]
|
|
250
|
+
|
|
251
|
+
new_ieqs = ((1,) + tuple(-v) for v in t_verts)
|
|
252
|
+
if self.n_vertices() == 1:
|
|
253
|
+
new_verts = self.vertices()
|
|
254
|
+
elif not self.n_equations():
|
|
255
|
+
new_verts = ((-h/h[0])[1:] for h in t_ieqs)
|
|
256
|
+
else:
|
|
257
|
+
# Transform the equations such that the normals are pairwise orthogonal.
|
|
258
|
+
t_eqns = list(t_eqns)
|
|
259
|
+
for i, h in enumerate(t_eqns):
|
|
260
|
+
for h1 in t_eqns[:i]:
|
|
261
|
+
a = h[1:]*h1[1:]
|
|
262
|
+
if a:
|
|
263
|
+
b = h1[1:]*h1[1:]
|
|
264
|
+
t_eqns[i] = b*h - a*h1
|
|
265
|
+
|
|
266
|
+
def move_vertex_to_subspace(vertex):
|
|
267
|
+
for h in t_eqns:
|
|
268
|
+
offset = vertex*h[1:]+h[0]
|
|
269
|
+
vertex = vertex-h[1:]*offset/(h[1:]*h[1:])
|
|
270
|
+
return vertex
|
|
271
|
+
|
|
272
|
+
new_verts = (move_vertex_to_subspace((-h/h[0])[1:]) for h in t_ieqs)
|
|
273
|
+
|
|
274
|
+
pref_rep = 'Hrep' if self.n_vertices() <= self.n_inequalities() else 'Vrep'
|
|
275
|
+
|
|
276
|
+
return parent.element_class(parent, [new_verts, [], []],
|
|
277
|
+
[new_ieqs, t_eqns],
|
|
278
|
+
Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
|
|
279
|
+
|
|
280
|
+
def pyramid(self):
|
|
281
|
+
"""
|
|
282
|
+
Return a polyhedron that is a pyramid over the original.
|
|
283
|
+
|
|
284
|
+
EXAMPLES::
|
|
285
|
+
|
|
286
|
+
sage: square = polytopes.hypercube(2); square
|
|
287
|
+
A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 4 vertices
|
|
288
|
+
sage: egyptian_pyramid = square.pyramid(); egyptian_pyramid
|
|
289
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 5 vertices
|
|
290
|
+
sage: egyptian_pyramid.n_vertices()
|
|
291
|
+
5
|
|
292
|
+
sage: for v in egyptian_pyramid.vertex_generator(): print(v)
|
|
293
|
+
A vertex at (0, -1, -1)
|
|
294
|
+
A vertex at (0, -1, 1)
|
|
295
|
+
A vertex at (0, 1, -1)
|
|
296
|
+
A vertex at (0, 1, 1)
|
|
297
|
+
A vertex at (1, 0, 0)
|
|
298
|
+
|
|
299
|
+
TESTS::
|
|
300
|
+
|
|
301
|
+
sage: polytopes.simplex(backend='cdd').pyramid().backend() # needs cddexec_gmp
|
|
302
|
+
'cdd'
|
|
303
|
+
"""
|
|
304
|
+
assert self.is_compact(), "Not a polytope."
|
|
305
|
+
c = self.center()
|
|
306
|
+
|
|
307
|
+
from itertools import chain
|
|
308
|
+
new_verts = chain(([0] + x for x in self.Vrep_generator()),
|
|
309
|
+
[[1] + list(c)])
|
|
310
|
+
new_ieqs = chain(([i.b()] + [-c*i.A() - i.b()] + list(i.A()) for i in self.inequalities()),
|
|
311
|
+
[[0, 1] + [0]*self.ambient_dim()])
|
|
312
|
+
new_eqns = ([e.b()] + [0] + list(e.A()) for e in self.equations())
|
|
313
|
+
|
|
314
|
+
pref_rep = 'Hrep' if self.n_vertices() > self.n_inequalities() else 'Vrep'
|
|
315
|
+
parent = self.parent().base_extend(self.center().parent(), ambient_dim=self.ambient_dim()+1)
|
|
316
|
+
|
|
317
|
+
if self.n_vertices() == 1:
|
|
318
|
+
# Fix the polyhedron with one vertex.
|
|
319
|
+
return parent.element_class(parent, [new_verts, [], []], None)
|
|
320
|
+
|
|
321
|
+
return parent.element_class(parent, [new_verts, [], []],
|
|
322
|
+
[new_ieqs, new_eqns],
|
|
323
|
+
Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
|
|
324
|
+
|
|
325
|
+
def _test_pyramid(self, tester=None, **options):
|
|
326
|
+
"""
|
|
327
|
+
Run tests on the methods related to pyramids.
|
|
328
|
+
|
|
329
|
+
TESTS:
|
|
330
|
+
|
|
331
|
+
sage: polytopes.regular_polygon(4)._test_pyramid() # needs sage.rings.number_field
|
|
332
|
+
"""
|
|
333
|
+
if tester is None:
|
|
334
|
+
tester = self._tester(**options)
|
|
335
|
+
|
|
336
|
+
def check_pyramid_certificate(P, cert):
|
|
337
|
+
others = set(v for v in P.vertices() if not v == cert)
|
|
338
|
+
if others:
|
|
339
|
+
tester.assertTrue(any(set(f.ambient_Vrepresentation()) == others for f in P.facets()))
|
|
340
|
+
|
|
341
|
+
if self.is_compact():
|
|
342
|
+
b, cert = self.is_pyramid(certificate=True)
|
|
343
|
+
if b:
|
|
344
|
+
check_pyramid_certificate(self, cert)
|
|
345
|
+
|
|
346
|
+
if 1 < self.n_vertices() < 50 and self.n_facets() < 50:
|
|
347
|
+
pyr = self.pyramid()
|
|
348
|
+
b, cert = pyr.is_pyramid(certificate=True)
|
|
349
|
+
tester.assertTrue(b)
|
|
350
|
+
check_pyramid_certificate(pyr, cert)
|
|
351
|
+
else:
|
|
352
|
+
with tester.assertRaises(AssertionError):
|
|
353
|
+
pyr = self.pyramid()
|
|
354
|
+
|
|
355
|
+
if self.is_compact() and 1 < self.n_vertices() < 50 and self.n_facets() < 50:
|
|
356
|
+
# Check the pyramid of the polar.
|
|
357
|
+
self_fraction_field = self.base_extend(QQ)
|
|
358
|
+
|
|
359
|
+
polar = self_fraction_field.polar(in_affine_span=True)
|
|
360
|
+
pyr_polar = polar.pyramid()
|
|
361
|
+
b, cert = pyr_polar.is_pyramid(certificate=True)
|
|
362
|
+
tester.assertTrue(b)
|
|
363
|
+
check_pyramid_certificate(pyr_polar, cert)
|
|
364
|
+
|
|
365
|
+
pyr = self_fraction_field.pyramid()
|
|
366
|
+
polar_pyr = pyr.polar(in_affine_span=True)
|
|
367
|
+
b, cert = polar_pyr.is_pyramid(certificate=True)
|
|
368
|
+
tester.assertTrue(b)
|
|
369
|
+
check_pyramid_certificate(polar_pyr, cert)
|
|
370
|
+
|
|
371
|
+
try:
|
|
372
|
+
import sage.graphs.graph
|
|
373
|
+
except ImportError:
|
|
374
|
+
pass
|
|
375
|
+
else:
|
|
376
|
+
tester.assertTrue(pyr_polar.is_combinatorially_isomorphic(pyr_polar))
|
|
377
|
+
|
|
378
|
+
# Basic properties of the pyramid.
|
|
379
|
+
|
|
380
|
+
# Check that the prism preserves the backend.
|
|
381
|
+
tester.assertEqual(pyr.backend(), self.backend())
|
|
382
|
+
|
|
383
|
+
tester.assertEqual(1 + self.n_vertices(), pyr.n_vertices())
|
|
384
|
+
tester.assertEqual(self.n_equations(), pyr.n_equations())
|
|
385
|
+
tester.assertEqual(1 + self.n_inequalities(), pyr.n_inequalities())
|
|
386
|
+
|
|
387
|
+
if self.n_vertices() < 15 and self.n_facets() < 15:
|
|
388
|
+
pyr._test_basic_properties()
|
|
389
|
+
|
|
390
|
+
def bipyramid(self):
|
|
391
|
+
"""
|
|
392
|
+
Return a polyhedron that is a bipyramid over the original.
|
|
393
|
+
|
|
394
|
+
EXAMPLES::
|
|
395
|
+
|
|
396
|
+
sage: octahedron = polytopes.cross_polytope(3)
|
|
397
|
+
sage: cross_poly_4d = octahedron.bipyramid()
|
|
398
|
+
sage: cross_poly_4d.n_vertices()
|
|
399
|
+
8
|
|
400
|
+
sage: q = [list(v) for v in cross_poly_4d.vertex_generator()]; q
|
|
401
|
+
[[-1, 0, 0, 0],
|
|
402
|
+
[0, -1, 0, 0],
|
|
403
|
+
[0, 0, -1, 0],
|
|
404
|
+
[0, 0, 0, -1],
|
|
405
|
+
[0, 0, 0, 1],
|
|
406
|
+
[0, 0, 1, 0],
|
|
407
|
+
[0, 1, 0, 0],
|
|
408
|
+
[1, 0, 0, 0]]
|
|
409
|
+
|
|
410
|
+
Now check that bipyramids of cross-polytopes are cross-polytopes::
|
|
411
|
+
|
|
412
|
+
sage: q2 = [list(v) for v in polytopes.cross_polytope(4).vertex_generator()]
|
|
413
|
+
sage: [v in q2 for v in q]
|
|
414
|
+
[True, True, True, True, True, True, True, True]
|
|
415
|
+
|
|
416
|
+
TESTS::
|
|
417
|
+
|
|
418
|
+
sage: polytopes.simplex(backend='cdd').bipyramid().backend() # needs cddexec_gmp
|
|
419
|
+
'cdd'
|
|
420
|
+
"""
|
|
421
|
+
c = self.center()
|
|
422
|
+
from itertools import chain
|
|
423
|
+
new_verts = chain(([0] + list(x) for x in self.vertex_generator()),
|
|
424
|
+
[[1] + list(c), [-1] + list(c)])
|
|
425
|
+
new_rays = ([0] + r for r in self.rays())
|
|
426
|
+
new_lines = ([0] + l for l in self.lines())
|
|
427
|
+
new_ieqs = chain(([i.b()] + [ c*i.A() + i.b()] + list(i.A()) for i in self.inequalities()),
|
|
428
|
+
([i.b()] + [-c*i.A() - i.b()] + list(i.A()) for i in self.inequalities()))
|
|
429
|
+
new_eqns = ([e.b()] + [0] + list(e.A()) for e in self.equations())
|
|
430
|
+
|
|
431
|
+
pref_rep = 'Hrep' if 2 + (self.n_vertices() + self.n_rays()) >= 2*self.n_inequalities() else 'Vrep'
|
|
432
|
+
parent = self.parent().base_extend(self.center().parent(), ambient_dim=self.ambient_dim()+1)
|
|
433
|
+
|
|
434
|
+
if c not in self.relative_interior():
|
|
435
|
+
# Fix polyhedra with non-proper center.
|
|
436
|
+
return parent.element_class(parent, [new_verts, new_rays, new_lines], None)
|
|
437
|
+
|
|
438
|
+
return parent.element_class(parent, [new_verts, new_rays, new_lines],
|
|
439
|
+
[new_ieqs, new_eqns],
|
|
440
|
+
Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
|
|
441
|
+
|
|
442
|
+
def _test_bipyramid(self, tester=None, **options):
|
|
443
|
+
"""
|
|
444
|
+
Run tests on the method :meth:`.bipyramid`.
|
|
445
|
+
|
|
446
|
+
TESTS::
|
|
447
|
+
|
|
448
|
+
sage: polytopes.cross_polytope(3)._test_bipyramid()
|
|
449
|
+
"""
|
|
450
|
+
if tester is None:
|
|
451
|
+
tester = self._tester(**options)
|
|
452
|
+
|
|
453
|
+
if (self.n_vertices() + self.n_rays() >= 40
|
|
454
|
+
or self.n_facets() >= 40
|
|
455
|
+
or self.n_vertices() <= 1):
|
|
456
|
+
return
|
|
457
|
+
|
|
458
|
+
bipyramid = self.bipyramid()
|
|
459
|
+
|
|
460
|
+
# Check that the double description is set up correctly.
|
|
461
|
+
if self.base_ring().is_exact() and self.n_vertices() + self.n_rays() < 15 and self.n_facets() < 15:
|
|
462
|
+
bipyramid._test_basic_properties(tester)
|
|
463
|
+
|
|
464
|
+
# Check that the bipyramid preserves the backend.
|
|
465
|
+
tester.assertEqual(bipyramid.backend(), self.backend())
|
|
466
|
+
|
|
467
|
+
if self.center() not in self.relative_interior():
|
|
468
|
+
# In this case (unbounded) the bipyramid behaves a bit different.
|
|
469
|
+
return
|
|
470
|
+
|
|
471
|
+
tester.assertEqual(2 + self.n_vertices(), bipyramid.n_vertices())
|
|
472
|
+
tester.assertEqual(self.n_rays(), bipyramid.n_rays())
|
|
473
|
+
tester.assertEqual(self.n_lines(), bipyramid.n_lines())
|
|
474
|
+
tester.assertEqual(self.n_equations(), bipyramid.n_equations())
|
|
475
|
+
tester.assertEqual(2*self.n_inequalities(), bipyramid.n_inequalities())
|
|
476
|
+
|
|
477
|
+
if not self.is_compact():
|
|
478
|
+
# ``is_bipyramid`` is only implemented for compact polyhedra.
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
b, cert = bipyramid.is_bipyramid(certificate=True)
|
|
482
|
+
tester.assertTrue(b)
|
|
483
|
+
|
|
484
|
+
if not self.is_bipyramid() and self.base_ring().is_exact():
|
|
485
|
+
# In this case the certificate is unique.
|
|
486
|
+
|
|
487
|
+
R = self.base_ring()
|
|
488
|
+
a = (R(1),) + tuple(self.center())
|
|
489
|
+
b = (R(-1),) + tuple(self.center())
|
|
490
|
+
c, d = (tuple(v) for v in cert)
|
|
491
|
+
tester.assertEqual(sorted([a, b]), sorted([c, d]))
|
|
492
|
+
|
|
493
|
+
def prism(self):
|
|
494
|
+
"""
|
|
495
|
+
Return a prism of the original polyhedron.
|
|
496
|
+
|
|
497
|
+
EXAMPLES::
|
|
498
|
+
|
|
499
|
+
sage: square = polytopes.hypercube(2)
|
|
500
|
+
sage: cube = square.prism()
|
|
501
|
+
sage: cube
|
|
502
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
|
|
503
|
+
sage: hypercube = cube.prism()
|
|
504
|
+
sage: hypercube.n_vertices()
|
|
505
|
+
16
|
|
506
|
+
|
|
507
|
+
TESTS::
|
|
508
|
+
|
|
509
|
+
sage: polytopes.simplex(backend='cdd').prism().backend() # needs cddexec_gmp
|
|
510
|
+
'cdd'
|
|
511
|
+
"""
|
|
512
|
+
from itertools import chain
|
|
513
|
+
new_verts = chain(([0] + v for v in self.vertices()),
|
|
514
|
+
([1] + v for v in self.vertices()))
|
|
515
|
+
new_rays = ([0] + r for r in self.rays())
|
|
516
|
+
new_lines = ([0] + l for l in self.lines())
|
|
517
|
+
new_eqns = ([e.b()] + [0] + list(e[1:]) for e in self.equations())
|
|
518
|
+
new_ieqs = chain(([i.b()] + [0] + list(i[1:]) for i in self.inequalities()),
|
|
519
|
+
[[0, 1] + [0]*self.ambient_dim(), [1, -1] + [0]*self.ambient_dim()])
|
|
520
|
+
|
|
521
|
+
pref_rep = 'Hrep' if 2*(self.n_vertices() + self.n_rays()) >= self.n_inequalities() + 2 else 'Vrep'
|
|
522
|
+
parent = self.parent().change_ring(self.base_ring(), ambient_dim=self.ambient_dim()+1)
|
|
523
|
+
|
|
524
|
+
if not self.vertices():
|
|
525
|
+
# Fix the empty polyhedron.
|
|
526
|
+
return parent.element_class(parent, [[], [], []], None)
|
|
527
|
+
|
|
528
|
+
return parent.element_class(parent, [new_verts, new_rays, new_lines],
|
|
529
|
+
[new_ieqs, new_eqns],
|
|
530
|
+
Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
|
|
531
|
+
|
|
532
|
+
def _test_prism(self, tester=None, **options):
|
|
533
|
+
"""
|
|
534
|
+
Run tests on the method :meth:`.prism`.
|
|
535
|
+
|
|
536
|
+
TESTS::
|
|
537
|
+
|
|
538
|
+
sage: polytopes.cross_polytope(3)._test_prism()
|
|
539
|
+
"""
|
|
540
|
+
if tester is None:
|
|
541
|
+
tester = self._tester(**options)
|
|
542
|
+
|
|
543
|
+
if self.n_vertices() + self.n_rays() < 40 and self.n_facets() < 40:
|
|
544
|
+
prism = self.prism()
|
|
545
|
+
|
|
546
|
+
# Check that the double description is set up correctly.
|
|
547
|
+
if self.base_ring().is_exact() and self.n_vertices() + self.n_rays() < 15 and self.n_facets() < 15:
|
|
548
|
+
prism._test_basic_properties(tester)
|
|
549
|
+
|
|
550
|
+
# Check that the prism preserves the backend.
|
|
551
|
+
tester.assertEqual(prism.backend(), self.backend())
|
|
552
|
+
|
|
553
|
+
tester.assertEqual(2*self.n_vertices(), prism.n_vertices())
|
|
554
|
+
tester.assertEqual(self.n_rays(), prism.n_rays())
|
|
555
|
+
tester.assertEqual(self.n_lines(), prism.n_lines())
|
|
556
|
+
tester.assertEqual(self.n_equations(), prism.n_equations())
|
|
557
|
+
if self.is_empty():
|
|
558
|
+
return
|
|
559
|
+
|
|
560
|
+
tester.assertEqual(2 + self.n_inequalities(), prism.n_inequalities())
|
|
561
|
+
|
|
562
|
+
if not self.is_compact():
|
|
563
|
+
# ``is_prism`` only implemented for compact polyhedra.
|
|
564
|
+
return
|
|
565
|
+
|
|
566
|
+
b, cert = prism.is_prism(certificate=True)
|
|
567
|
+
tester.assertTrue(b)
|
|
568
|
+
|
|
569
|
+
if not self.is_prism() and self.base_ring().is_exact():
|
|
570
|
+
# In this case the certificate is unique.
|
|
571
|
+
|
|
572
|
+
R = self.base_ring()
|
|
573
|
+
cert_set = set(frozenset(tuple(v) for v in f) for f in cert)
|
|
574
|
+
expected_cert = set(frozenset((i,) + tuple(v)
|
|
575
|
+
for v in self.vertices())
|
|
576
|
+
for i in (R(0), R(1)))
|
|
577
|
+
tester.assertEqual(cert_set, expected_cert)
|
|
578
|
+
|
|
579
|
+
def truncation(self, cut_frac=None):
|
|
580
|
+
r"""
|
|
581
|
+
Return a new polyhedron formed from two points on each edge
|
|
582
|
+
between two vertices.
|
|
583
|
+
|
|
584
|
+
INPUT:
|
|
585
|
+
|
|
586
|
+
- ``cut_frac`` -- integer; how deeply to cut into the edge
|
|
587
|
+
Default is `\frac{1}{3}`
|
|
588
|
+
|
|
589
|
+
OUTPUT: a Polyhedron object, truncated as described above
|
|
590
|
+
|
|
591
|
+
EXAMPLES::
|
|
592
|
+
|
|
593
|
+
sage: cube = polytopes.hypercube(3)
|
|
594
|
+
sage: trunc_cube = cube.truncation()
|
|
595
|
+
sage: trunc_cube.n_vertices()
|
|
596
|
+
24
|
|
597
|
+
sage: trunc_cube.n_inequalities()
|
|
598
|
+
14
|
|
599
|
+
|
|
600
|
+
TESTS::
|
|
601
|
+
|
|
602
|
+
sage: polytopes.simplex(backend='field').truncation().backend()
|
|
603
|
+
'field'
|
|
604
|
+
"""
|
|
605
|
+
if cut_frac is None:
|
|
606
|
+
cut_frac = ZZ.one() / 3
|
|
607
|
+
|
|
608
|
+
new_vertices = []
|
|
609
|
+
for e in self.bounded_edges():
|
|
610
|
+
new_vertices.append((1 - cut_frac) * e[0]() + cut_frac * e[1]())
|
|
611
|
+
new_vertices.append(cut_frac * e[0]() + (1 - cut_frac) * e[1]())
|
|
612
|
+
|
|
613
|
+
new_vertices = [list(v) for v in new_vertices]
|
|
614
|
+
new_rays = self.rays()
|
|
615
|
+
new_lines = self.lines()
|
|
616
|
+
|
|
617
|
+
parent = self.parent().base_extend(cut_frac)
|
|
618
|
+
return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
|
|
619
|
+
|
|
620
|
+
def lawrence_polytope(self):
|
|
621
|
+
r"""
|
|
622
|
+
Return the Lawrence polytope of ``self``.
|
|
623
|
+
|
|
624
|
+
Let `P` be a `d`-polytope in `\RR^r` with `n` vertices. The Lawrence
|
|
625
|
+
polytope of `P` is the polytope whose vertices are the columns of the
|
|
626
|
+
following `(r+n)`-by-`2n` matrix.
|
|
627
|
+
|
|
628
|
+
.. MATH::
|
|
629
|
+
|
|
630
|
+
\begin{pmatrix}
|
|
631
|
+
V & V \\
|
|
632
|
+
I_n & 2I_n
|
|
633
|
+
\end{pmatrix},
|
|
634
|
+
|
|
635
|
+
where `V` is the `r`-by-`n` vertices matrix of `P`.
|
|
636
|
+
|
|
637
|
+
EXAMPLES::
|
|
638
|
+
|
|
639
|
+
sage: P = polytopes.octahedron()
|
|
640
|
+
sage: L = P.lawrence_polytope(); L
|
|
641
|
+
A 9-dimensional polyhedron in ZZ^9 defined as the convex hull of 12 vertices
|
|
642
|
+
sage: V = P.vertices_list()
|
|
643
|
+
sage: for i, v in enumerate(V):
|
|
644
|
+
....: v = v + i*[0]
|
|
645
|
+
....: P = P.lawrence_extension(v)
|
|
646
|
+
sage: P == L
|
|
647
|
+
True
|
|
648
|
+
|
|
649
|
+
REFERENCES:
|
|
650
|
+
|
|
651
|
+
For more information, see Section 6.6 of [Zie2007]_.
|
|
652
|
+
"""
|
|
653
|
+
from sage.matrix.constructor import block_matrix
|
|
654
|
+
|
|
655
|
+
if not self.is_compact():
|
|
656
|
+
raise NotImplementedError("self must be a polytope")
|
|
657
|
+
|
|
658
|
+
V = self.vertices_matrix().transpose()
|
|
659
|
+
n = self.n_vertices()
|
|
660
|
+
I_n = matrix.identity(n)
|
|
661
|
+
lambda_V = block_matrix([[V, I_n], [V, 2*I_n]])
|
|
662
|
+
parent = self.parent().change_ring(self.base_ring(), ambient_dim=self.ambient_dim() + n)
|
|
663
|
+
return parent.element_class(parent, [lambda_V, [], []], None)
|
|
664
|
+
|
|
665
|
+
def deformation_cone(self):
|
|
666
|
+
r"""
|
|
667
|
+
Return the deformation cone of ``self``.
|
|
668
|
+
|
|
669
|
+
Let `P` be a `d`-polytope in `\RR^r` with `n` facets. The deformation
|
|
670
|
+
cone is a polyhedron in `\RR^n` whose points are the right-hand side `b`
|
|
671
|
+
in `Ax\leq b` where `A` is the matrix of facet normals of ``self``, so
|
|
672
|
+
that the resulting polytope has a normal fan which is a coarsening of
|
|
673
|
+
the normal fan of ``self``.
|
|
674
|
+
|
|
675
|
+
EXAMPLES:
|
|
676
|
+
|
|
677
|
+
Let's examine the deformation cone of the square with one truncated
|
|
678
|
+
vertex::
|
|
679
|
+
|
|
680
|
+
sage: tc = Polyhedron([(1, -1), (1/3, 1), (1, 1/3), (-1, 1), (-1, -1)])
|
|
681
|
+
sage: dc = tc.deformation_cone()
|
|
682
|
+
sage: dc.an_element()
|
|
683
|
+
(2, 1, 1, 0, 0)
|
|
684
|
+
sage: [_.A() for _ in tc.Hrepresentation()]
|
|
685
|
+
[(1, 0), (0, 1), (0, -1), (-3, -3), (-1, 0)]
|
|
686
|
+
sage: P = Polyhedron(rays=[(1, 0, 2), (0, 1, 1), (0, -1, 1), (-3, -3, 0), (-1, 0, 0)])
|
|
687
|
+
sage: P.rays()
|
|
688
|
+
(A ray in the direction (-1, -1, 0),
|
|
689
|
+
A ray in the direction (-1, 0, 0),
|
|
690
|
+
A ray in the direction (0, -1, 1),
|
|
691
|
+
A ray in the direction (0, 1, 1),
|
|
692
|
+
A ray in the direction (1, 0, 2))
|
|
693
|
+
|
|
694
|
+
Now, let's compute the deformation cone of the pyramid over a square
|
|
695
|
+
and verify that it is not full dimensional::
|
|
696
|
+
|
|
697
|
+
sage: py = Polyhedron([(0, -1, -1), (0, -1, 1), (0, 1, -1), (0, 1, 1), (1, 0, 0)])
|
|
698
|
+
sage: dc_py = py.deformation_cone(); dc_py
|
|
699
|
+
A 4-dimensional polyhedron in QQ^5 defined as the convex hull of 1 vertex, 1 ray, 3 lines
|
|
700
|
+
sage: [ineq.b() for ineq in py.Hrepresentation()]
|
|
701
|
+
[0, 1, 1, 1, 1]
|
|
702
|
+
sage: r = dc_py.rays()[0]
|
|
703
|
+
sage: l1,l2,l3 = dc_py.lines()
|
|
704
|
+
sage: r.vector()-l1.vector()/2-l2.vector()-l3.vector()/2
|
|
705
|
+
(0, 1, 1, 1, 1)
|
|
706
|
+
|
|
707
|
+
.. SEEALSO::
|
|
708
|
+
|
|
709
|
+
:meth:`~sage.schemes.toric.variety.Kaehler_cone`
|
|
710
|
+
|
|
711
|
+
REFERENCES:
|
|
712
|
+
|
|
713
|
+
For more information, see Section 5.4 of [DLRS2010]_ and Section
|
|
714
|
+
2.2 of [ACEP2020].
|
|
715
|
+
"""
|
|
716
|
+
from .constructor import Polyhedron
|
|
717
|
+
m = matrix([ineq.A() for ineq in self.Hrepresentation()])
|
|
718
|
+
m = m.transpose()
|
|
719
|
+
m_ker = m.right_kernel_matrix(basis='computed')
|
|
720
|
+
gale = tuple(m_ker.columns())
|
|
721
|
+
collection = (f.ambient_H_indices() for f in self.faces(0))
|
|
722
|
+
n = len(gale)
|
|
723
|
+
c = None
|
|
724
|
+
for cone_indices in collection:
|
|
725
|
+
dual_cone = Polyhedron(rays=[gale[i] for i in range(n) if i not in
|
|
726
|
+
cone_indices])
|
|
727
|
+
c = c.intersection(dual_cone) if c is not None else dual_cone
|
|
728
|
+
preimages = [m_ker.solve_right(r.vector()) for r in c.rays()]
|
|
729
|
+
return Polyhedron(lines=m.rows(), rays=preimages)
|
|
730
|
+
|
|
731
|
+
###########################################################
|
|
732
|
+
# Binary operations.
|
|
733
|
+
###########################################################
|
|
734
|
+
|
|
735
|
+
@coerce_binop
|
|
736
|
+
def minkowski_sum(self, other):
|
|
737
|
+
r"""
|
|
738
|
+
Return the Minkowski sum.
|
|
739
|
+
|
|
740
|
+
Minkowski addition of two subsets of a vector space is defined
|
|
741
|
+
as
|
|
742
|
+
|
|
743
|
+
.. MATH::
|
|
744
|
+
|
|
745
|
+
X \oplus Y =
|
|
746
|
+
\bigcup_{y\in Y} (X+y) =
|
|
747
|
+
\bigcup_{x\in X, y\in Y} (x+y)
|
|
748
|
+
|
|
749
|
+
See :meth:`minkowski_difference` for a partial inverse operation.
|
|
750
|
+
|
|
751
|
+
INPUT:
|
|
752
|
+
|
|
753
|
+
- ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base`
|
|
754
|
+
|
|
755
|
+
OUTPUT: the Minkowski sum of ``self`` and ``other``
|
|
756
|
+
|
|
757
|
+
EXAMPLES::
|
|
758
|
+
|
|
759
|
+
sage: X = polytopes.hypercube(3)
|
|
760
|
+
sage: Y = Polyhedron(vertices=[(0,0,0), (0,0,1/2), (0,1/2,0), (1/2,0,0)])
|
|
761
|
+
sage: X+Y
|
|
762
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 13 vertices
|
|
763
|
+
|
|
764
|
+
sage: four_cube = polytopes.hypercube(4)
|
|
765
|
+
sage: four_simplex = Polyhedron(vertices=[[0, 0, 0, 1], [0, 0, 1, 0],
|
|
766
|
+
....: [0, 1, 0, 0], [1, 0, 0, 0]])
|
|
767
|
+
sage: four_cube + four_simplex
|
|
768
|
+
A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 36 vertices
|
|
769
|
+
sage: four_cube.minkowski_sum(four_simplex) == four_cube + four_simplex
|
|
770
|
+
True
|
|
771
|
+
|
|
772
|
+
sage: poly_spam = Polyhedron([[3,4,5,2], [1,0,0,1], [0,0,0,0],
|
|
773
|
+
....: [0,4,3,2], [-3,-3,-3,-3]], base_ring=ZZ)
|
|
774
|
+
sage: poly_eggs = Polyhedron([[5,4,5,4], [-4,5,-4,5],
|
|
775
|
+
....: [4,-5,4,-5], [0,0,0,0]], base_ring=QQ)
|
|
776
|
+
sage: poly_spam + poly_spam + poly_eggs
|
|
777
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 12 vertices
|
|
778
|
+
"""
|
|
779
|
+
new_vertices = []
|
|
780
|
+
for v1 in self.vertex_generator():
|
|
781
|
+
for v2 in other.vertex_generator():
|
|
782
|
+
new_vertices.append(list(v1() + v2()))
|
|
783
|
+
if new_vertices != []:
|
|
784
|
+
new_rays = self.rays() + other.rays()
|
|
785
|
+
new_lines = self.lines() + other.lines()
|
|
786
|
+
return self.parent().element_class(self.parent(), [new_vertices, new_rays, new_lines], None)
|
|
787
|
+
else:
|
|
788
|
+
return self.parent().element_class(self.parent(), None, None)
|
|
789
|
+
|
|
790
|
+
_add_ = minkowski_sum
|
|
791
|
+
|
|
792
|
+
@coerce_binop
|
|
793
|
+
def minkowski_difference(self, other):
|
|
794
|
+
r"""
|
|
795
|
+
Return the Minkowski difference.
|
|
796
|
+
|
|
797
|
+
Minkowski subtraction can equivalently be defined via
|
|
798
|
+
Minkowski addition (see :meth:`minkowski_sum`) or as
|
|
799
|
+
set-theoretic intersection via
|
|
800
|
+
|
|
801
|
+
.. MATH::
|
|
802
|
+
|
|
803
|
+
X \ominus Y =
|
|
804
|
+
(X^c \oplus Y)^c =
|
|
805
|
+
\bigcap_{y\in Y} (X-y)
|
|
806
|
+
|
|
807
|
+
where superscript-"c" means the complement in the ambient
|
|
808
|
+
vector space. The Minkowski difference of convex sets is
|
|
809
|
+
convex, and the difference of polyhedra is again a
|
|
810
|
+
polyhedron. We only consider the case of polyhedra in the
|
|
811
|
+
following. Note that it is not quite the inverse of
|
|
812
|
+
addition. In fact:
|
|
813
|
+
|
|
814
|
+
* `(X+Y)-Y = X` for any polyhedra `X`, `Y`.
|
|
815
|
+
|
|
816
|
+
* `(X-Y)+Y \subseteq X`
|
|
817
|
+
|
|
818
|
+
* `(X-Y)+Y = X` if and only if Y is a Minkowski summand of X.
|
|
819
|
+
|
|
820
|
+
INPUT:
|
|
821
|
+
|
|
822
|
+
- ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base`
|
|
823
|
+
|
|
824
|
+
OUTPUT:
|
|
825
|
+
|
|
826
|
+
The Minkowski difference of ``self`` and ``other``. Also known
|
|
827
|
+
as Minkowski subtraction of ``other`` from ``self``.
|
|
828
|
+
|
|
829
|
+
EXAMPLES::
|
|
830
|
+
|
|
831
|
+
sage: X = polytopes.hypercube(3)
|
|
832
|
+
sage: Y = Polyhedron(vertices=[(0,0,0), (0,0,1), (0,1,0), (1,0,0)]) / 2
|
|
833
|
+
sage: (X+Y)-Y == X
|
|
834
|
+
True
|
|
835
|
+
sage: (X-Y)+Y < X
|
|
836
|
+
True
|
|
837
|
+
|
|
838
|
+
The polyhedra need not be full-dimensional::
|
|
839
|
+
|
|
840
|
+
sage: X2 = Polyhedron(vertices=[(-1,-1,0), (1,-1,0), (-1,1,0), (1,1,0)])
|
|
841
|
+
sage: Y2 = Polyhedron(vertices=[(0,0,0), (0,1,0), (1,0,0)]) / 2
|
|
842
|
+
sage: (X2+Y2)-Y2 == X2
|
|
843
|
+
True
|
|
844
|
+
sage: (X2-Y2)+Y2 < X2
|
|
845
|
+
True
|
|
846
|
+
|
|
847
|
+
Minus sign is really an alias for :meth:`minkowski_difference`
|
|
848
|
+
::
|
|
849
|
+
|
|
850
|
+
sage: four_cube = polytopes.hypercube(4)
|
|
851
|
+
sage: four_simplex = Polyhedron(vertices=[[0, 0, 0, 1], [0, 0, 1, 0],
|
|
852
|
+
....: [0, 1, 0, 0], [1, 0, 0, 0]])
|
|
853
|
+
sage: four_cube - four_simplex
|
|
854
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 16 vertices
|
|
855
|
+
sage: four_cube.minkowski_difference(four_simplex) == four_cube - four_simplex
|
|
856
|
+
True
|
|
857
|
+
|
|
858
|
+
Coercion of the base ring works::
|
|
859
|
+
|
|
860
|
+
sage: poly_spam = Polyhedron([[3,4,5,2], [1,0,0,1], [0,0,0,0],
|
|
861
|
+
....: [0,4,3,2], [-3,-3,-3,-3]], base_ring=ZZ)
|
|
862
|
+
sage: poly_eggs = Polyhedron([[5,4,5,4], [-4,5,-4,5],
|
|
863
|
+
....: [4,-5,4,-5], [0,0,0,0]], base_ring=QQ) / 100
|
|
864
|
+
sage: poly_spam - poly_eggs
|
|
865
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 5 vertices
|
|
866
|
+
|
|
867
|
+
TESTS::
|
|
868
|
+
|
|
869
|
+
sage: X = polytopes.hypercube(2)
|
|
870
|
+
sage: Y = Polyhedron(vertices=[(1,1)])
|
|
871
|
+
sage: (X-Y).Vrepresentation()
|
|
872
|
+
(A vertex at (0, -2), A vertex at (0, 0), A vertex at (-2, 0), A vertex at (-2, -2))
|
|
873
|
+
|
|
874
|
+
sage: Y = Polyhedron(vertices=[(1,1), (0,0)])
|
|
875
|
+
sage: (X-Y).Vrepresentation()
|
|
876
|
+
(A vertex at (0, -1), A vertex at (0, 0), A vertex at (-1, 0), A vertex at (-1, -1))
|
|
877
|
+
|
|
878
|
+
sage: X = X + Y # now Y is a Minkowski summand of X
|
|
879
|
+
sage: (X+Y)-Y == X
|
|
880
|
+
True
|
|
881
|
+
sage: (X-Y)+Y == X
|
|
882
|
+
True
|
|
883
|
+
|
|
884
|
+
Testing that :issue:`28506` is fixed::
|
|
885
|
+
|
|
886
|
+
sage: Q = Polyhedron([[1,0],[0,1]])
|
|
887
|
+
sage: S = Polyhedron([[0,0],[1,2]])
|
|
888
|
+
sage: S.minkowski_difference(Q)
|
|
889
|
+
A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices
|
|
890
|
+
"""
|
|
891
|
+
if other.is_empty():
|
|
892
|
+
return self.parent().universe() # empty intersection = everything
|
|
893
|
+
if not other.is_compact():
|
|
894
|
+
raise NotImplementedError('only subtracting compact polyhedra is implemented')
|
|
895
|
+
new_eqns = []
|
|
896
|
+
for eq in self.equations():
|
|
897
|
+
values = [ eq.A() * v.vector() for v in other.vertices() ]
|
|
898
|
+
eq = list(eq)
|
|
899
|
+
eq[0] += min(values) # shift constant term
|
|
900
|
+
new_eqns.append(eq)
|
|
901
|
+
P = self.parent()
|
|
902
|
+
new_ieqs = []
|
|
903
|
+
for ieq in self.inequalities():
|
|
904
|
+
values = [ ieq.A() * v.vector() for v in other.vertices() ]
|
|
905
|
+
ieq = list(ieq)
|
|
906
|
+
ieq[0] += min(values) # shift constant term
|
|
907
|
+
new_ieqs.append(ieq)
|
|
908
|
+
|
|
909
|
+
# Some vertices might need fractions.
|
|
910
|
+
P = self.parent().change_ring(self.base_ring().fraction_field())
|
|
911
|
+
return P.element_class(P, None, [new_ieqs, new_eqns])
|
|
912
|
+
|
|
913
|
+
def __sub__(self, other):
|
|
914
|
+
r"""
|
|
915
|
+
Implement minus binary operation.
|
|
916
|
+
|
|
917
|
+
Polyhedra are not a ring with respect to dilatation and
|
|
918
|
+
Minkowski sum, for example `X\oplus(-1)*Y \not= X\ominus Y`.
|
|
919
|
+
|
|
920
|
+
INPUT:
|
|
921
|
+
|
|
922
|
+
- ``other`` -- a translation vector or a polyhedron
|
|
923
|
+
|
|
924
|
+
OUTPUT:
|
|
925
|
+
|
|
926
|
+
Either translation by the negative of the given vector or
|
|
927
|
+
Minkowski subtraction by the given polyhedron.
|
|
928
|
+
|
|
929
|
+
EXAMPLES::
|
|
930
|
+
|
|
931
|
+
sage: X = polytopes.hypercube(2)
|
|
932
|
+
sage: v = vector([1,1])
|
|
933
|
+
sage: (X - v/2).Vrepresentation()
|
|
934
|
+
(A vertex at (-3/2, -3/2), A vertex at (-3/2, 1/2),
|
|
935
|
+
A vertex at (1/2, -3/2), A vertex at (1/2, 1/2))
|
|
936
|
+
sage: (X-v)+v == X
|
|
937
|
+
True
|
|
938
|
+
|
|
939
|
+
sage: Y = Polyhedron(vertices=[(1/2,0), (0,1/2)])
|
|
940
|
+
sage: (X-Y).Vrepresentation()
|
|
941
|
+
(A vertex at (1/2, -1), A vertex at (1/2, 1/2),
|
|
942
|
+
A vertex at (-1, 1/2), A vertex at (-1, -1))
|
|
943
|
+
sage: (X+Y)-Y == X
|
|
944
|
+
True
|
|
945
|
+
"""
|
|
946
|
+
if isinstance(other, Polyhedron_base5):
|
|
947
|
+
return self.minkowski_difference(other)
|
|
948
|
+
return self + (-other)
|
|
949
|
+
|
|
950
|
+
def product(self, other):
|
|
951
|
+
"""
|
|
952
|
+
Return the Cartesian product.
|
|
953
|
+
|
|
954
|
+
INPUT:
|
|
955
|
+
|
|
956
|
+
- ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base`
|
|
957
|
+
|
|
958
|
+
OUTPUT:
|
|
959
|
+
|
|
960
|
+
The Cartesian product of ``self`` and ``other`` with a
|
|
961
|
+
suitable base ring to encompass the two.
|
|
962
|
+
|
|
963
|
+
EXAMPLES::
|
|
964
|
+
|
|
965
|
+
sage: P1 = Polyhedron([[0], [1]], base_ring=ZZ)
|
|
966
|
+
sage: P2 = Polyhedron([[0], [1]], base_ring=QQ)
|
|
967
|
+
sage: P1.product(P2)
|
|
968
|
+
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
|
|
969
|
+
|
|
970
|
+
The Cartesian product is the product in the semiring of polyhedra::
|
|
971
|
+
|
|
972
|
+
sage: P1 * P1
|
|
973
|
+
A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 4 vertices
|
|
974
|
+
sage: P1 * P2
|
|
975
|
+
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
|
|
976
|
+
sage: P2 * P2
|
|
977
|
+
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
|
|
978
|
+
sage: 2 * P1
|
|
979
|
+
A 1-dimensional polyhedron in ZZ^1 defined as the convex hull of 2 vertices
|
|
980
|
+
sage: P1 * 2.0 # needs cddexec
|
|
981
|
+
A 1-dimensional polyhedron in RDF^1 defined as the convex hull of 2 vertices
|
|
982
|
+
|
|
983
|
+
An alias is :meth:`cartesian_product`::
|
|
984
|
+
|
|
985
|
+
sage: P1.cartesian_product(P2) == P1.product(P2)
|
|
986
|
+
True
|
|
987
|
+
|
|
988
|
+
TESTS:
|
|
989
|
+
|
|
990
|
+
Check that :issue:`15253` is fixed::
|
|
991
|
+
|
|
992
|
+
sage: polytopes.hypercube(1) * polytopes.hypercube(2)
|
|
993
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices
|
|
994
|
+
"""
|
|
995
|
+
try:
|
|
996
|
+
new_ring = self.parent()._coerce_base_ring(other)
|
|
997
|
+
except TypeError:
|
|
998
|
+
raise TypeError("no common canonical parent for objects with parents: " + str(self.parent())
|
|
999
|
+
+ " and " + str(other.parent()))
|
|
1000
|
+
|
|
1001
|
+
from itertools import chain
|
|
1002
|
+
|
|
1003
|
+
new_vertices = (tuple(x) + tuple(y)
|
|
1004
|
+
for x in self.vertex_generator() for y in other.vertex_generator())
|
|
1005
|
+
|
|
1006
|
+
self_zero = tuple(0 for _ in range( self.ambient_dim()))
|
|
1007
|
+
other_zero = tuple(0 for _ in range(other.ambient_dim()))
|
|
1008
|
+
|
|
1009
|
+
rays = chain((tuple(r) + other_zero for r in self.ray_generator()),
|
|
1010
|
+
(self_zero + tuple(r) for r in other.ray_generator()))
|
|
1011
|
+
|
|
1012
|
+
lines = chain((tuple(l) + other_zero for l in self.line_generator()),
|
|
1013
|
+
(self_zero + tuple(l) for l in other.line_generator()))
|
|
1014
|
+
|
|
1015
|
+
if self.n_vertices() == 0 or other.n_vertices() == 0:
|
|
1016
|
+
# In this case we obtain the empty polyhedron.
|
|
1017
|
+
# There is not vertex to attach the rays or lines to.
|
|
1018
|
+
# By our convention, in this case the polyhedron shall also not have rays or lines.
|
|
1019
|
+
rays = ()
|
|
1020
|
+
lines = ()
|
|
1021
|
+
|
|
1022
|
+
ieqs = chain((tuple(i) + other_zero
|
|
1023
|
+
for i in self.inequality_generator()),
|
|
1024
|
+
((i.b(),) + self_zero + tuple(i.A())
|
|
1025
|
+
for i in other.inequality_generator()))
|
|
1026
|
+
|
|
1027
|
+
eqns = chain((tuple(e) + other_zero
|
|
1028
|
+
for e in self.equation_generator()),
|
|
1029
|
+
((e.b(),) + self_zero + tuple(e.A())
|
|
1030
|
+
for e in other.equation_generator()))
|
|
1031
|
+
|
|
1032
|
+
pref_rep = 'Vrep' if self.n_vertices() + self.n_rays() + other.n_vertices() + other.n_rays() \
|
|
1033
|
+
<= self.n_inequalities() + other.n_inequalities() else 'Hrep'
|
|
1034
|
+
|
|
1035
|
+
parent = self.parent().change_ring(new_ring, ambient_dim=self.ambient_dim() + other.ambient_dim())
|
|
1036
|
+
return parent.element_class(parent, [new_vertices, rays, lines],
|
|
1037
|
+
[ieqs, eqns],
|
|
1038
|
+
Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
|
|
1039
|
+
|
|
1040
|
+
_mul_ = product
|
|
1041
|
+
|
|
1042
|
+
cartesian_product = product
|
|
1043
|
+
|
|
1044
|
+
def _test_product(self, tester=None, **options):
|
|
1045
|
+
"""
|
|
1046
|
+
Run tests on the method :meth:`.product`.
|
|
1047
|
+
|
|
1048
|
+
TESTS::
|
|
1049
|
+
|
|
1050
|
+
sage: polytopes.cross_polytope(3)._test_product()
|
|
1051
|
+
"""
|
|
1052
|
+
from sage.rings.real_double import RDF
|
|
1053
|
+
from .library import polytopes
|
|
1054
|
+
|
|
1055
|
+
if tester is None:
|
|
1056
|
+
tester = self._tester(**options)
|
|
1057
|
+
|
|
1058
|
+
if self.n_vertices() + self.n_rays() < 40 and self.n_facets() < 40:
|
|
1059
|
+
# Check that the product preserves the backend, where possible.
|
|
1060
|
+
try:
|
|
1061
|
+
P = polytopes.simplex(backend='cdd')
|
|
1062
|
+
except FeatureNotPresentError:
|
|
1063
|
+
pass
|
|
1064
|
+
else:
|
|
1065
|
+
tester.assertEqual((self*P).backend(), self.backend())
|
|
1066
|
+
Q = polytopes.simplex(backend='ppl')
|
|
1067
|
+
tester.assertEqual((self*Q).backend(), self.backend())
|
|
1068
|
+
|
|
1069
|
+
# And that it changes the backend correctly where necessary.
|
|
1070
|
+
try:
|
|
1071
|
+
from sage.rings.qqbar import AA
|
|
1072
|
+
import sage.libs.pari
|
|
1073
|
+
except ImportError:
|
|
1074
|
+
pass
|
|
1075
|
+
else:
|
|
1076
|
+
if self.base_ring() is not AA and AA.has_coerce_map_from(self.base_ring()):
|
|
1077
|
+
R = self*polytopes.regular_polygon(5, exact=True)
|
|
1078
|
+
assert R
|
|
1079
|
+
if RDF.has_coerce_map_from(self.base_ring()):
|
|
1080
|
+
try:
|
|
1081
|
+
P5 = polytopes.regular_polygon(5, exact=False)
|
|
1082
|
+
except FeatureNotPresentError:
|
|
1083
|
+
pass
|
|
1084
|
+
else:
|
|
1085
|
+
R = self * P5
|
|
1086
|
+
assert R
|
|
1087
|
+
|
|
1088
|
+
if self.base_ring() in (ZZ, QQ):
|
|
1089
|
+
# Check that the double description is set up correctly.
|
|
1090
|
+
self_field = self.base_extend(self.base_ring(), backend='field')
|
|
1091
|
+
try:
|
|
1092
|
+
P = polytopes.permutahedron(4, backend='field').base_extend(QQ)
|
|
1093
|
+
except ImportError:
|
|
1094
|
+
pass
|
|
1095
|
+
else:
|
|
1096
|
+
(self_field * P)._test_basic_properties(tester)
|
|
1097
|
+
from .constructor import Polyhedron
|
|
1098
|
+
Q = Polyhedron(rays=[[1,0,0,0],[0,1,1,0]], lines=[[0,1,0,1]], backend='field')
|
|
1099
|
+
(self_field * Q)._test_basic_properties(tester)
|
|
1100
|
+
|
|
1101
|
+
def join(self, other):
|
|
1102
|
+
"""
|
|
1103
|
+
Return the join of ``self`` and ``other``.
|
|
1104
|
+
|
|
1105
|
+
The join of two polyhedra is obtained by first placing the two objects in
|
|
1106
|
+
two non-intersecting affine subspaces `V`, and `W` whose affine hull is
|
|
1107
|
+
the whole ambient space, and finally by taking the convex hull of their
|
|
1108
|
+
union. The dimension of the join is the sum of the dimensions of the
|
|
1109
|
+
two polyhedron plus 1.
|
|
1110
|
+
|
|
1111
|
+
INPUT:
|
|
1112
|
+
|
|
1113
|
+
- ``other`` -- a polyhedron
|
|
1114
|
+
|
|
1115
|
+
EXAMPLES::
|
|
1116
|
+
|
|
1117
|
+
sage: P1 = Polyhedron([[0],[1]], base_ring=ZZ)
|
|
1118
|
+
sage: P2 = Polyhedron([[0],[1]], base_ring=QQ)
|
|
1119
|
+
sage: P1.join(P2)
|
|
1120
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices
|
|
1121
|
+
sage: P1.join(P1)
|
|
1122
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices
|
|
1123
|
+
sage: P2.join(P2)
|
|
1124
|
+
A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices
|
|
1125
|
+
|
|
1126
|
+
An unbounded example::
|
|
1127
|
+
|
|
1128
|
+
sage: R1 = Polyhedron(rays=[[1]])
|
|
1129
|
+
sage: R1.join(R1)
|
|
1130
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 2 vertices and 2 rays
|
|
1131
|
+
|
|
1132
|
+
TESTS::
|
|
1133
|
+
|
|
1134
|
+
sage: C = polytopes.hypercube(5)
|
|
1135
|
+
sage: S = Polyhedron([[1]])
|
|
1136
|
+
sage: C.join(S).is_combinatorially_isomorphic(C.pyramid()) # needs sage.graphs
|
|
1137
|
+
True
|
|
1138
|
+
|
|
1139
|
+
sage: # needs cddexec_gmp
|
|
1140
|
+
sage: P = polytopes.simplex(backend='cdd')
|
|
1141
|
+
sage: Q = polytopes.simplex(backend='ppl')
|
|
1142
|
+
sage: P.join(Q).backend()
|
|
1143
|
+
'cdd'
|
|
1144
|
+
sage: Q.join(P).backend()
|
|
1145
|
+
'ppl'
|
|
1146
|
+
|
|
1147
|
+
Check that the double description is set up correctly::
|
|
1148
|
+
|
|
1149
|
+
sage: P = polytopes.cross_polytope(4)
|
|
1150
|
+
sage: P1 = polytopes.cross_polytope(4, backend='field')
|
|
1151
|
+
sage: P.join(P) == P1.join(P1)
|
|
1152
|
+
True
|
|
1153
|
+
|
|
1154
|
+
sage: P = 4*polytopes.hypercube(4)
|
|
1155
|
+
sage: P1 = 4*polytopes.hypercube(4, backend='field')
|
|
1156
|
+
sage: P.join(P) == P1.join(P1)
|
|
1157
|
+
True
|
|
1158
|
+
|
|
1159
|
+
sage: P = polytopes.permutahedron(4)
|
|
1160
|
+
sage: P1 = polytopes.permutahedron(4, backend='field')
|
|
1161
|
+
sage: P.join(P) == P1.join(P1)
|
|
1162
|
+
True
|
|
1163
|
+
"""
|
|
1164
|
+
try:
|
|
1165
|
+
new_ring = self.parent()._coerce_base_ring(other)
|
|
1166
|
+
except TypeError:
|
|
1167
|
+
raise TypeError("no common canonical parent for objects with parents: " + str(self.parent())
|
|
1168
|
+
+ " and " + str(other.parent()))
|
|
1169
|
+
|
|
1170
|
+
from itertools import chain
|
|
1171
|
+
|
|
1172
|
+
dim_self = self.ambient_dim()
|
|
1173
|
+
dim_other = other.ambient_dim()
|
|
1174
|
+
parent = self.parent().change_ring(new_ring, ambient_dim=self.ambient_dim() + other.ambient_dim() + 1)
|
|
1175
|
+
|
|
1176
|
+
new_vertices1 = (list(x) + [0]*dim_other + [0] for x in self.vertex_generator())
|
|
1177
|
+
new_vertices2 = ([0]*dim_self + list(x) + [1] for x in other.vertex_generator())
|
|
1178
|
+
new_vertices = chain(new_vertices1, new_vertices2)
|
|
1179
|
+
|
|
1180
|
+
new_rays1 = (list(r) + [0]*dim_other + [0] for r in self.ray_generator())
|
|
1181
|
+
new_rays2 = ([0]*dim_self + list(r) + [1] for r in other.ray_generator())
|
|
1182
|
+
new_rays = chain(new_rays1, new_rays2)
|
|
1183
|
+
|
|
1184
|
+
new_lines1 = (list(l) + [0]*dim_other + [0] for l in self.line_generator())
|
|
1185
|
+
new_lines2 = ([0]*dim_self + list(l) + [1] for l in other.line_generator())
|
|
1186
|
+
new_lines = chain(new_lines1, new_lines2)
|
|
1187
|
+
|
|
1188
|
+
if not self.is_compact() or not other.is_compact() or self.n_vertices() <= 1 or other.n_vertices() <= 1:
|
|
1189
|
+
# Cases for which the below double description does not work.
|
|
1190
|
+
return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
|
|
1191
|
+
|
|
1192
|
+
# Facet defining inequalities that contain the corresponding vertices from ``new_vertices1``
|
|
1193
|
+
# and all vertices from ``new_vertices2``.
|
|
1194
|
+
new_inequalities1 = ([i[0]] + list(i[1:]) + [0]*dim_other + [-i[0]] for i in self.inequality_generator())
|
|
1195
|
+
|
|
1196
|
+
# Facet defining inequalities that contain the corresponding vertices from ``new_vertices2``
|
|
1197
|
+
# and all vertices from ``new_vertices1``.
|
|
1198
|
+
new_inequalities2 = ([0] + [0]*dim_self + list(i[1:]) + [i[0]] for i in other.inequality_generator())
|
|
1199
|
+
|
|
1200
|
+
new_inequalities = chain(new_inequalities1, new_inequalities2)
|
|
1201
|
+
|
|
1202
|
+
# Equations that all vertices corresponding to ``new_vertices1`` satisfy.
|
|
1203
|
+
# For any vertex from ``new_vertices2`` the condition is trivial.
|
|
1204
|
+
new_equations1 = ([e[0]] + list(e[1:]) + [0]*dim_other + [-e[0]] for e in self.equation_generator())
|
|
1205
|
+
|
|
1206
|
+
# Equations that all vertices corresponding to ``new_vertices2`` satisfy.
|
|
1207
|
+
# For any vertex from ``new_vertices1`` the condition is trivial.
|
|
1208
|
+
new_equations2 = ([0] + [0]*dim_self + list(e[1:]) + [e[0]] for e in other.equation_generator())
|
|
1209
|
+
|
|
1210
|
+
new_equations = chain(new_equations1, new_equations2)
|
|
1211
|
+
|
|
1212
|
+
new_n_inequalities = self.n_inequalities() + other.n_inequalities()
|
|
1213
|
+
new_n_vertices = self.n_vertices() + other.n_vertices()
|
|
1214
|
+
new_n_rays = self.n_rays() + other.n_rays()
|
|
1215
|
+
|
|
1216
|
+
pref_rep = 'Vrep' if new_n_vertices + new_n_rays <= new_n_inequalities else 'Hrep'
|
|
1217
|
+
|
|
1218
|
+
return parent.element_class(parent,
|
|
1219
|
+
[new_vertices, new_rays, new_lines],
|
|
1220
|
+
[new_inequalities, new_equations],
|
|
1221
|
+
Vrep_minimal=True, Hrep_minimal=True,
|
|
1222
|
+
pref_rep=pref_rep)
|
|
1223
|
+
|
|
1224
|
+
def subdirect_sum(self, other):
|
|
1225
|
+
"""
|
|
1226
|
+
Return the subdirect sum of ``self`` and ``other``.
|
|
1227
|
+
|
|
1228
|
+
The subdirect sum of two polyhedron is a projection of the join of the
|
|
1229
|
+
two polytopes. It is obtained by placing the two objects in orthogonal subspaces
|
|
1230
|
+
intersecting at the origin.
|
|
1231
|
+
|
|
1232
|
+
INPUT:
|
|
1233
|
+
|
|
1234
|
+
- ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base`
|
|
1235
|
+
|
|
1236
|
+
EXAMPLES::
|
|
1237
|
+
|
|
1238
|
+
sage: P1 = Polyhedron([[1], [2]], base_ring=ZZ)
|
|
1239
|
+
sage: P2 = Polyhedron([[3], [4]], base_ring=QQ)
|
|
1240
|
+
sage: sds = P1.subdirect_sum(P2); sds
|
|
1241
|
+
A 2-dimensional polyhedron in QQ^2
|
|
1242
|
+
defined as the convex hull of 4 vertices
|
|
1243
|
+
sage: sds.vertices()
|
|
1244
|
+
(A vertex at (0, 3),
|
|
1245
|
+
A vertex at (0, 4),
|
|
1246
|
+
A vertex at (1, 0),
|
|
1247
|
+
A vertex at (2, 0))
|
|
1248
|
+
|
|
1249
|
+
.. SEEALSO::
|
|
1250
|
+
|
|
1251
|
+
:meth:`join`
|
|
1252
|
+
:meth:`direct_sum`
|
|
1253
|
+
|
|
1254
|
+
TESTS::
|
|
1255
|
+
|
|
1256
|
+
sage: # needs cddexec_gmp
|
|
1257
|
+
sage: P = polytopes.simplex(backend='cdd')
|
|
1258
|
+
sage: Q = polytopes.simplex(backend='ppl')
|
|
1259
|
+
sage: P.subdirect_sum(Q).backend()
|
|
1260
|
+
'cdd'
|
|
1261
|
+
sage: Q.subdirect_sum(P).backend()
|
|
1262
|
+
'ppl'
|
|
1263
|
+
"""
|
|
1264
|
+
try:
|
|
1265
|
+
new_ring = self.parent()._coerce_base_ring(other)
|
|
1266
|
+
except TypeError:
|
|
1267
|
+
raise TypeError("no common canonical parent for objects with parents: " + str(self.parent())
|
|
1268
|
+
+ " and " + str(other.parent()))
|
|
1269
|
+
|
|
1270
|
+
dim_self = self.ambient_dim()
|
|
1271
|
+
dim_other = other.ambient_dim()
|
|
1272
|
+
|
|
1273
|
+
new_vertices = [list(x)+[0]*dim_other for x in self.vertex_generator()] + \
|
|
1274
|
+
[[0]*dim_self+list(x) for x in other.vertex_generator()]
|
|
1275
|
+
new_rays = []
|
|
1276
|
+
new_rays.extend( [ r+[0]*dim_other
|
|
1277
|
+
for r in self.ray_generator() ] )
|
|
1278
|
+
new_rays.extend( [ [0]*dim_self+r
|
|
1279
|
+
for r in other.ray_generator() ] )
|
|
1280
|
+
new_lines = []
|
|
1281
|
+
new_lines.extend( [ l+[0]*dim_other
|
|
1282
|
+
for l in self.line_generator() ] )
|
|
1283
|
+
new_lines.extend( [ [0]*dim_self+l
|
|
1284
|
+
for l in other.line_generator() ] )
|
|
1285
|
+
|
|
1286
|
+
parent = self.parent().change_ring(new_ring, ambient_dim=self.ambient_dim() + other.ambient_dim())
|
|
1287
|
+
return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
|
|
1288
|
+
|
|
1289
|
+
def direct_sum(self, other):
|
|
1290
|
+
"""
|
|
1291
|
+
Return the direct sum of ``self`` and ``other``.
|
|
1292
|
+
|
|
1293
|
+
The direct sum of two polyhedron is the subdirect sum of the two, when
|
|
1294
|
+
they have the origin in their interior. To avoid checking if the origin
|
|
1295
|
+
is contained in both, we place the affine subspace containing ``other``
|
|
1296
|
+
at the center of ``self``.
|
|
1297
|
+
|
|
1298
|
+
INPUT:
|
|
1299
|
+
|
|
1300
|
+
- ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base`
|
|
1301
|
+
|
|
1302
|
+
EXAMPLES::
|
|
1303
|
+
|
|
1304
|
+
sage: P1 = Polyhedron([[1], [2]], base_ring=ZZ)
|
|
1305
|
+
sage: P2 = Polyhedron([[3], [4]], base_ring=QQ)
|
|
1306
|
+
sage: ds = P1.direct_sum(P2);ds
|
|
1307
|
+
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
|
|
1308
|
+
sage: ds.vertices()
|
|
1309
|
+
(A vertex at (1, 0),
|
|
1310
|
+
A vertex at (2, 0),
|
|
1311
|
+
A vertex at (3/2, -1/2),
|
|
1312
|
+
A vertex at (3/2, 1/2))
|
|
1313
|
+
|
|
1314
|
+
.. SEEALSO::
|
|
1315
|
+
|
|
1316
|
+
:meth:`join`
|
|
1317
|
+
:meth:`subdirect_sum`
|
|
1318
|
+
|
|
1319
|
+
TESTS:
|
|
1320
|
+
|
|
1321
|
+
Check that the backend is preserved::
|
|
1322
|
+
|
|
1323
|
+
sage: # needs cddexec_gmp
|
|
1324
|
+
sage: P = polytopes.simplex(backend='cdd')
|
|
1325
|
+
sage: Q = polytopes.simplex(backend='ppl')
|
|
1326
|
+
sage: P.direct_sum(Q).backend()
|
|
1327
|
+
'cdd'
|
|
1328
|
+
sage: Q.direct_sum(P).backend()
|
|
1329
|
+
'ppl'
|
|
1330
|
+
|
|
1331
|
+
Check that :issue:`28506` is fixed::
|
|
1332
|
+
|
|
1333
|
+
sage: s2 = polytopes.simplex(2)
|
|
1334
|
+
sage: s3 = polytopes.simplex(3)
|
|
1335
|
+
sage: s2.direct_sum(s3)
|
|
1336
|
+
A 5-dimensional polyhedron in QQ^7 defined as the convex hull of 7 vertices
|
|
1337
|
+
"""
|
|
1338
|
+
try:
|
|
1339
|
+
# Some vertices might need fractions.
|
|
1340
|
+
new_ring = self.parent()._coerce_base_ring(other).fraction_field()
|
|
1341
|
+
except TypeError:
|
|
1342
|
+
raise TypeError("no common canonical parent for objects with parents: " + str(self.parent())
|
|
1343
|
+
+ " and " + str(other.parent()))
|
|
1344
|
+
|
|
1345
|
+
dim_self = self.ambient_dim()
|
|
1346
|
+
dim_other = other.ambient_dim()
|
|
1347
|
+
|
|
1348
|
+
new_vertices = [list(x) + [0]*dim_other for x in self.vertex_generator()] + \
|
|
1349
|
+
[list(self.center()) + list(x.vector() - other.center()) for x in other.vertex_generator()]
|
|
1350
|
+
new_rays = []
|
|
1351
|
+
new_rays.extend( [ r + [0]*dim_other
|
|
1352
|
+
for r in self.ray_generator() ] )
|
|
1353
|
+
new_rays.extend( [ [0]*dim_self + r
|
|
1354
|
+
for r in other.ray_generator() ] )
|
|
1355
|
+
new_lines = []
|
|
1356
|
+
new_lines.extend( [ l + [0]*dim_other
|
|
1357
|
+
for l in self.line_generator() ] )
|
|
1358
|
+
new_lines.extend( [ [0]*dim_self + l
|
|
1359
|
+
for l in other.line_generator() ] )
|
|
1360
|
+
|
|
1361
|
+
parent = self.parent().change_ring(new_ring, ambient_dim=self.ambient_dim() + other.ambient_dim())
|
|
1362
|
+
return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
|
|
1363
|
+
|
|
1364
|
+
@coerce_binop
|
|
1365
|
+
def convex_hull(self, other):
|
|
1366
|
+
"""
|
|
1367
|
+
Return the convex hull of the set-theoretic union of the two
|
|
1368
|
+
polyhedra.
|
|
1369
|
+
|
|
1370
|
+
INPUT:
|
|
1371
|
+
|
|
1372
|
+
- ``other`` -- a :class:`Polyhedron`
|
|
1373
|
+
|
|
1374
|
+
OUTPUT: the convex hull
|
|
1375
|
+
|
|
1376
|
+
EXAMPLES::
|
|
1377
|
+
|
|
1378
|
+
sage: # needs cddexec
|
|
1379
|
+
sage: a_simplex = polytopes.simplex(3, project=True)
|
|
1380
|
+
sage: verts = a_simplex.vertices()
|
|
1381
|
+
sage: verts = [[x[0]*3/5 + x[1]*4/5, -x[0]*4/5 + x[1]*3/5, x[2]] for x in verts]
|
|
1382
|
+
sage: another_simplex = Polyhedron(vertices=verts)
|
|
1383
|
+
sage: simplex_union = a_simplex.convex_hull(another_simplex)
|
|
1384
|
+
sage: simplex_union.n_vertices()
|
|
1385
|
+
7
|
|
1386
|
+
"""
|
|
1387
|
+
hull_vertices = self.vertices() + other.vertices()
|
|
1388
|
+
hull_rays = self.rays() + other.rays()
|
|
1389
|
+
hull_lines = self.lines() + other.lines()
|
|
1390
|
+
return self.parent().element_class(self.parent(), [hull_vertices, hull_rays, hull_lines], None)
|
|
1391
|
+
|
|
1392
|
+
@coerce_binop
|
|
1393
|
+
def intersection(self, other):
|
|
1394
|
+
r"""
|
|
1395
|
+
Return the intersection of one polyhedron with another.
|
|
1396
|
+
|
|
1397
|
+
INPUT:
|
|
1398
|
+
|
|
1399
|
+
- ``other`` -- a :class:`Polyhedron`
|
|
1400
|
+
|
|
1401
|
+
OUTPUT: the intersection
|
|
1402
|
+
|
|
1403
|
+
Note that the intersection of two `\ZZ`-polyhedra might not be
|
|
1404
|
+
a `\ZZ`-polyhedron. In this case, a `\QQ`-polyhedron is
|
|
1405
|
+
returned.
|
|
1406
|
+
|
|
1407
|
+
EXAMPLES::
|
|
1408
|
+
|
|
1409
|
+
sage: cube = polytopes.hypercube(3)
|
|
1410
|
+
sage: oct = polytopes.cross_polytope(3)
|
|
1411
|
+
sage: cube.intersection(oct*2)
|
|
1412
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 12 vertices
|
|
1413
|
+
|
|
1414
|
+
As a shorthand, one may use::
|
|
1415
|
+
|
|
1416
|
+
sage: cube & oct*2
|
|
1417
|
+
A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 12 vertices
|
|
1418
|
+
|
|
1419
|
+
The intersection of two `\ZZ`-polyhedra is not necessarily a `\ZZ`-polyhedron::
|
|
1420
|
+
|
|
1421
|
+
sage: P = Polyhedron([(0,0),(1,1)], base_ring=ZZ)
|
|
1422
|
+
sage: P.intersection(P)
|
|
1423
|
+
A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices
|
|
1424
|
+
sage: Q = Polyhedron([(0,1),(1,0)], base_ring=ZZ)
|
|
1425
|
+
sage: P.intersection(Q)
|
|
1426
|
+
A 0-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex
|
|
1427
|
+
sage: _.Vrepresentation()
|
|
1428
|
+
(A vertex at (1/2, 1/2),)
|
|
1429
|
+
|
|
1430
|
+
TESTS:
|
|
1431
|
+
|
|
1432
|
+
Check that :issue:`19012` is fixed::
|
|
1433
|
+
|
|
1434
|
+
sage: # needs sage.rings.number_field
|
|
1435
|
+
sage: K.<a> = QuadraticField(5)
|
|
1436
|
+
sage: P = Polyhedron([[0, 0], [0, a], [1, 1]])
|
|
1437
|
+
sage: Q = Polyhedron(ieqs=[[-1, a, 1]])
|
|
1438
|
+
sage: P.intersection(Q)
|
|
1439
|
+
A 2-dimensional polyhedron in
|
|
1440
|
+
(Number Field in a with defining polynomial x^2 - 5 with a = 2.236067977499790?)^2
|
|
1441
|
+
defined as the convex hull of 4 vertices
|
|
1442
|
+
"""
|
|
1443
|
+
new_ieqs = self.inequalities() + other.inequalities()
|
|
1444
|
+
new_eqns = self.equations() + other.equations()
|
|
1445
|
+
parent = self.parent()
|
|
1446
|
+
try:
|
|
1447
|
+
intersection = parent.element_class(parent, None, [new_ieqs, new_eqns])
|
|
1448
|
+
|
|
1449
|
+
# Force calculation of the vertices.
|
|
1450
|
+
_ = intersection.n_vertices()
|
|
1451
|
+
return intersection
|
|
1452
|
+
except TypeError as msg:
|
|
1453
|
+
if self.base_ring() is ZZ:
|
|
1454
|
+
parent = parent.base_extend(QQ)
|
|
1455
|
+
return parent.element_class(parent, None, [new_ieqs, new_eqns])
|
|
1456
|
+
else:
|
|
1457
|
+
raise TypeError(msg)
|
|
1458
|
+
|
|
1459
|
+
__and__ = intersection
|
|
1460
|
+
|
|
1461
|
+
###########################################################
|
|
1462
|
+
# Actions.
|
|
1463
|
+
###########################################################
|
|
1464
|
+
|
|
1465
|
+
def _acted_upon_(self, actor, self_on_left):
|
|
1466
|
+
"""
|
|
1467
|
+
Implement the action by scalars, vectors, matrices or other polyhedra.
|
|
1468
|
+
|
|
1469
|
+
INPUT:
|
|
1470
|
+
|
|
1471
|
+
- ``actor`` -- one of the following:
|
|
1472
|
+
- a scalar, not necessarily in :meth:`base_ring`,
|
|
1473
|
+
- a :class:`Polyhedron`,
|
|
1474
|
+
- a :class:`sage.modules.free_module_element.vector`,
|
|
1475
|
+
- a :class:`sage.matrix.constructor.matrix`,
|
|
1476
|
+
- ``self_on_right`` -- must be ``False`` for actor a matrix;
|
|
1477
|
+
ignored otherwise
|
|
1478
|
+
|
|
1479
|
+
OUTPUT:
|
|
1480
|
+
|
|
1481
|
+
- Dilation for a scalar
|
|
1482
|
+
- Product for a polyhedron
|
|
1483
|
+
- Translation for a vector
|
|
1484
|
+
- Linear transformation for a matrix
|
|
1485
|
+
|
|
1486
|
+
EXAMPLES:
|
|
1487
|
+
|
|
1488
|
+
``actor`` is a scalar::
|
|
1489
|
+
|
|
1490
|
+
sage: p = Polyhedron(vertices = [[t,t^2,t^3] for t in srange(2,6)])
|
|
1491
|
+
sage: p._acted_upon_(2, True) == p.dilation(2)
|
|
1492
|
+
True
|
|
1493
|
+
sage: p*2 == p.dilation(2)
|
|
1494
|
+
True
|
|
1495
|
+
|
|
1496
|
+
``actor`` is a polyhedron::
|
|
1497
|
+
|
|
1498
|
+
sage: p*p == p.product(p)
|
|
1499
|
+
True
|
|
1500
|
+
|
|
1501
|
+
``actor`` is a vector::
|
|
1502
|
+
|
|
1503
|
+
sage: p + vector(ZZ,[1,2,3]) == p.translation([1,2,3])
|
|
1504
|
+
True
|
|
1505
|
+
|
|
1506
|
+
``actor`` is a matrix::
|
|
1507
|
+
|
|
1508
|
+
sage: matrix(ZZ,[[1,2,3]]) * p
|
|
1509
|
+
A 1-dimensional polyhedron in ZZ^1 defined as the convex hull of 2 vertices
|
|
1510
|
+
|
|
1511
|
+
A matrix must act from the left::
|
|
1512
|
+
|
|
1513
|
+
sage: p * matrix(ZZ, [[1,2,3]]*3)
|
|
1514
|
+
Traceback (most recent call last):
|
|
1515
|
+
...
|
|
1516
|
+
ValueError: matrices should act on the left
|
|
1517
|
+
"""
|
|
1518
|
+
if isinstance(actor, Polyhedron_base5):
|
|
1519
|
+
return self.product(actor)
|
|
1520
|
+
elif isinstance(actor, Vector):
|
|
1521
|
+
return self.translation(actor)
|
|
1522
|
+
elif isinstance(actor, Matrix):
|
|
1523
|
+
if self_on_left:
|
|
1524
|
+
raise ValueError("matrices should act on the left")
|
|
1525
|
+
else:
|
|
1526
|
+
return self.linear_transformation(actor)
|
|
1527
|
+
else:
|
|
1528
|
+
return self.dilation(actor)
|
|
1529
|
+
|
|
1530
|
+
def translation(self, displacement):
|
|
1531
|
+
"""
|
|
1532
|
+
Return the translated polyhedron.
|
|
1533
|
+
|
|
1534
|
+
INPUT:
|
|
1535
|
+
|
|
1536
|
+
- ``displacement`` -- a displacement vector or a list/tuple of
|
|
1537
|
+
coordinates that determines a displacement vector
|
|
1538
|
+
|
|
1539
|
+
OUTPUT: the translated polyhedron
|
|
1540
|
+
|
|
1541
|
+
.. SEEALSO:: :meth:`linear_transformation`, :meth:`dilation`
|
|
1542
|
+
|
|
1543
|
+
EXAMPLES::
|
|
1544
|
+
|
|
1545
|
+
sage: P = Polyhedron([[0,0], [1,0], [0,1]], base_ring=ZZ)
|
|
1546
|
+
sage: P.translation([2,1])
|
|
1547
|
+
A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices
|
|
1548
|
+
sage: P.translation(vector(QQ, [2,1]))
|
|
1549
|
+
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices
|
|
1550
|
+
|
|
1551
|
+
TESTS::
|
|
1552
|
+
|
|
1553
|
+
sage: P = Polyhedron([[0,0], [1,0], [0,1]], base_ring=ZZ, backend='field')
|
|
1554
|
+
sage: P.translation([2,1]).backend()
|
|
1555
|
+
'field'
|
|
1556
|
+
|
|
1557
|
+
Check that precomputed data is set up correctly::
|
|
1558
|
+
|
|
1559
|
+
sage: P = polytopes.permutahedron(4)*Polyhedron(lines=[[1]])
|
|
1560
|
+
sage: Q = P.change_ring(P.base_ring(), backend='field')
|
|
1561
|
+
sage: P + vector([1,2,3,4,5]) == Q + vector([1,2,3,4,5])
|
|
1562
|
+
True
|
|
1563
|
+
sage: P + vector([1,2,3,4,5/2]) == Q + vector([1,2,3,4,5/2])
|
|
1564
|
+
True
|
|
1565
|
+
"""
|
|
1566
|
+
Vrep, Hrep, parent = self._translation_double_description(displacement)
|
|
1567
|
+
|
|
1568
|
+
pref_rep = 'Vrep' if self.n_vertices() + self.n_rays() <= self.n_inequalities() else 'Hrep'
|
|
1569
|
+
|
|
1570
|
+
return parent.element_class(parent, Vrep, Hrep,
|
|
1571
|
+
Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
|
|
1572
|
+
|
|
1573
|
+
def _translation_double_description(self, displacement):
|
|
1574
|
+
r"""
|
|
1575
|
+
Return the input parameters for the translation.
|
|
1576
|
+
|
|
1577
|
+
INPUT:
|
|
1578
|
+
|
|
1579
|
+
- ``displacement`` -- a displacement vector or a list/tuple of
|
|
1580
|
+
coordinates that determines a displacement vector
|
|
1581
|
+
|
|
1582
|
+
OUTPUT: tuple of consisting of new Vrepresentation, Hrepresentation and parent
|
|
1583
|
+
|
|
1584
|
+
.. SEEALSO::
|
|
1585
|
+
|
|
1586
|
+
:meth:`translation`
|
|
1587
|
+
|
|
1588
|
+
EXAMPLES::
|
|
1589
|
+
|
|
1590
|
+
sage: P = Polyhedron([[0,0], [1,0], [0,1]], base_ring=ZZ)
|
|
1591
|
+
sage: Vrep, Hrep, parent = P._translation_double_description([2,1])
|
|
1592
|
+
sage: [tuple(x) for x in Vrep], [tuple(x) for x in Hrep], parent
|
|
1593
|
+
([((2, 1), (2, 2), (3, 1)), (), ()],
|
|
1594
|
+
[((-2, 1, 0), (-1, 0, 1), (4, -1, -1)), ()],
|
|
1595
|
+
Polyhedra in ZZ^2)
|
|
1596
|
+
"""
|
|
1597
|
+
displacement = vector(displacement)
|
|
1598
|
+
new_vertices = (x.vector()+displacement for x in self.vertex_generator())
|
|
1599
|
+
new_rays = self.rays()
|
|
1600
|
+
new_lines = self.lines()
|
|
1601
|
+
parent = self.parent().base_extend(displacement)
|
|
1602
|
+
|
|
1603
|
+
# Replace a hyperplane of the form A*x + b >= 0 by
|
|
1604
|
+
# A(x-displacement) + b >= 0 <=> Ax + b - A*displacement >= 0.
|
|
1605
|
+
# Likewise for equations.
|
|
1606
|
+
def get_new(x):
|
|
1607
|
+
y = x.vector().change_ring(parent.base_ring())
|
|
1608
|
+
y[0] -= x.A()*displacement
|
|
1609
|
+
return y
|
|
1610
|
+
|
|
1611
|
+
new_ieqs = (get_new(x) for x in self.inequality_generator())
|
|
1612
|
+
new_eqns = (get_new(x) for x in self.equation_generator())
|
|
1613
|
+
return [new_vertices, new_rays, new_lines], [new_ieqs, new_eqns], parent
|
|
1614
|
+
|
|
1615
|
+
def dilation(self, scalar):
|
|
1616
|
+
"""
|
|
1617
|
+
Return the dilated (uniformly stretched) polyhedron.
|
|
1618
|
+
|
|
1619
|
+
INPUT:
|
|
1620
|
+
|
|
1621
|
+
- ``scalar`` -- a scalar, not necessarily in :meth:`base_ring`
|
|
1622
|
+
|
|
1623
|
+
OUTPUT:
|
|
1624
|
+
|
|
1625
|
+
The polyhedron dilated by that scalar, possibly coerced to a
|
|
1626
|
+
bigger base ring.
|
|
1627
|
+
|
|
1628
|
+
.. SEEALSO:: :meth:`linear_transformation`, :meth:`translation`
|
|
1629
|
+
|
|
1630
|
+
EXAMPLES::
|
|
1631
|
+
|
|
1632
|
+
sage: p = Polyhedron(vertices=[[t,t^2,t^3] for t in srange(2,6)])
|
|
1633
|
+
sage: next(p.vertex_generator())
|
|
1634
|
+
A vertex at (2, 4, 8)
|
|
1635
|
+
sage: p2 = p.dilation(2)
|
|
1636
|
+
sage: next(p2.vertex_generator())
|
|
1637
|
+
A vertex at (4, 8, 16)
|
|
1638
|
+
sage: p.dilation(2) == p * 2
|
|
1639
|
+
True
|
|
1640
|
+
|
|
1641
|
+
TESTS:
|
|
1642
|
+
|
|
1643
|
+
Dilation of empty polyhedra works, see :issue:`14987`::
|
|
1644
|
+
|
|
1645
|
+
sage: p = Polyhedron(ambient_dim=2); p
|
|
1646
|
+
The empty polyhedron in ZZ^2
|
|
1647
|
+
sage: p.dilation(3)
|
|
1648
|
+
The empty polyhedron in ZZ^2
|
|
1649
|
+
|
|
1650
|
+
sage: p = Polyhedron(vertices=[(1,1)], rays=[(1,0)], lines=[(0,1)])
|
|
1651
|
+
sage: (-p).rays()
|
|
1652
|
+
(A ray in the direction (-1, 0),)
|
|
1653
|
+
sage: (-p).lines()
|
|
1654
|
+
(A line in the direction (0, 1),)
|
|
1655
|
+
|
|
1656
|
+
sage: (0*p).rays()
|
|
1657
|
+
()
|
|
1658
|
+
sage: (0*p).lines()
|
|
1659
|
+
()
|
|
1660
|
+
"""
|
|
1661
|
+
parent = self.parent().base_extend(scalar)
|
|
1662
|
+
|
|
1663
|
+
if scalar == 0:
|
|
1664
|
+
new_vertices = tuple(self.ambient_space().zero() for v in self.vertex_generator())
|
|
1665
|
+
new_rays = []
|
|
1666
|
+
new_lines = []
|
|
1667
|
+
return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
|
|
1668
|
+
|
|
1669
|
+
one = parent.base_ring().one()
|
|
1670
|
+
sign = one if scalar > 0 else -one
|
|
1671
|
+
|
|
1672
|
+
make_new_Hrep = lambda h: tuple(scalar*sign*x if i == 0 else sign*x
|
|
1673
|
+
for i, x in enumerate(h._vector))
|
|
1674
|
+
|
|
1675
|
+
new_vertices = (tuple(scalar*x for x in v._vector) for v in self.vertex_generator())
|
|
1676
|
+
new_rays = (tuple(sign*x for x in r._vector) for r in self.ray_generator())
|
|
1677
|
+
new_lines = self.line_generator()
|
|
1678
|
+
new_inequalities = map(make_new_Hrep, self.inequality_generator())
|
|
1679
|
+
new_equations = map(make_new_Hrep, self.equation_generator())
|
|
1680
|
+
|
|
1681
|
+
pref_rep = 'Vrep' if self.n_vertices() + self.n_rays() <= self.n_inequalities() else 'Hrep'
|
|
1682
|
+
|
|
1683
|
+
return parent.element_class(parent, [new_vertices, new_rays, new_lines],
|
|
1684
|
+
[new_inequalities, new_equations],
|
|
1685
|
+
Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
|
|
1686
|
+
|
|
1687
|
+
def __truediv__(self, scalar):
|
|
1688
|
+
"""
|
|
1689
|
+
Divide by a scalar factor.
|
|
1690
|
+
|
|
1691
|
+
See :meth:`dilation` for details.
|
|
1692
|
+
|
|
1693
|
+
EXAMPLES::
|
|
1694
|
+
|
|
1695
|
+
sage: p = Polyhedron(vertices = [[t,t^2,t^3] for t in srange(2,4)])
|
|
1696
|
+
sage: (p/5).Vrepresentation()
|
|
1697
|
+
(A vertex at (2/5, 4/5, 8/5), A vertex at (3/5, 9/5, 27/5))
|
|
1698
|
+
sage: (p/int(5)).Vrepresentation() # needs cddexec
|
|
1699
|
+
(A vertex at (0.4, 0.8, 1.6), A vertex at (0.6, 1.8, 5.4))
|
|
1700
|
+
"""
|
|
1701
|
+
return self.dilation(1/scalar)
|
|
1702
|
+
|
|
1703
|
+
def _test_dilation(self, tester=None, **options):
|
|
1704
|
+
"""
|
|
1705
|
+
Run tests on the method :meth:`.dilation`.
|
|
1706
|
+
|
|
1707
|
+
TESTS::
|
|
1708
|
+
|
|
1709
|
+
sage: polytopes.cross_polytope(3)._test_dilation()
|
|
1710
|
+
"""
|
|
1711
|
+
from sage.rings.real_double import RDF
|
|
1712
|
+
from .base import Polyhedron_base
|
|
1713
|
+
|
|
1714
|
+
if tester is None:
|
|
1715
|
+
tester = self._tester(**options)
|
|
1716
|
+
|
|
1717
|
+
# Testing that the backend is preserved.
|
|
1718
|
+
tester.assertEqual(self.dilation(2*self.base_ring().gen()).backend(), self.backend())
|
|
1719
|
+
tester.assertEqual(self.dilation(ZZ(3)).backend(), self.backend())
|
|
1720
|
+
|
|
1721
|
+
if self.n_vertices() + self.n_rays() > 40:
|
|
1722
|
+
# Avoid long time computations.
|
|
1723
|
+
return
|
|
1724
|
+
|
|
1725
|
+
# Testing that the double description is set up correctly.
|
|
1726
|
+
if self.base_ring().is_exact():
|
|
1727
|
+
if self.base_ring() in (QQ, ZZ):
|
|
1728
|
+
p = self.base_extend(self.base_ring(), backend='field')
|
|
1729
|
+
(ZZ(2) * p)._test_basic_properties(tester)
|
|
1730
|
+
(ZZ(2)/2 * p)._test_basic_properties(tester)
|
|
1731
|
+
(ZZ(-3) * p)._test_basic_properties(tester)
|
|
1732
|
+
(ZZ(-1)/2 * p)._test_basic_properties(tester)
|
|
1733
|
+
else:
|
|
1734
|
+
tester.assertIsInstance(ZZ(1)/3*self, Polyhedron_base)
|
|
1735
|
+
|
|
1736
|
+
try:
|
|
1737
|
+
from sage.rings.qqbar import AA
|
|
1738
|
+
import sage.libs.pari
|
|
1739
|
+
except ImportError:
|
|
1740
|
+
return
|
|
1741
|
+
|
|
1742
|
+
if self.n_vertices() > 20 or self.base_ring() is AA:
|
|
1743
|
+
# Avoid long time computations.
|
|
1744
|
+
return
|
|
1745
|
+
|
|
1746
|
+
# Some sanity check on the volume (only run for relatively small instances).
|
|
1747
|
+
if self.dim() > -1 and self.is_compact() and self.base_ring().is_exact():
|
|
1748
|
+
tester.assertEqual(self.dilation(3).volume(measure='induced'), self.volume(measure='induced')*3**self.dim())
|
|
1749
|
+
|
|
1750
|
+
# Testing coercion with algebraic numbers.
|
|
1751
|
+
try:
|
|
1752
|
+
from sage.rings.number_field.number_field import QuadraticField
|
|
1753
|
+
K1 = QuadraticField(2, embedding=AA(2).sqrt())
|
|
1754
|
+
sqrt2 = K1.gen()
|
|
1755
|
+
K2 = QuadraticField(3, embedding=AA(3).sqrt())
|
|
1756
|
+
sqrt3 = K2.gen()
|
|
1757
|
+
except ImportError:
|
|
1758
|
+
pass
|
|
1759
|
+
else:
|
|
1760
|
+
if self.base_ring() in (QQ, ZZ, AA, RDF):
|
|
1761
|
+
tester.assertIsInstance(sqrt2*self, Polyhedron_base)
|
|
1762
|
+
tester.assertIsInstance(sqrt3*self, Polyhedron_base)
|
|
1763
|
+
elif hasattr(self.base_ring(), "composite_fields"):
|
|
1764
|
+
for scalar, K in ((sqrt2, K1), (sqrt3, K2)):
|
|
1765
|
+
new_ring = None
|
|
1766
|
+
try:
|
|
1767
|
+
new_ring = self.base_ring().composite_fields()[0]
|
|
1768
|
+
except (KeyError, AttributeError, TypeError):
|
|
1769
|
+
# This isn't about testing composite fields.
|
|
1770
|
+
pass
|
|
1771
|
+
if new_ring:
|
|
1772
|
+
p = self.change_ring(new_ring)
|
|
1773
|
+
tester.assertIsInstance(scalar*p, Polyhedron_base)
|
|
1774
|
+
|
|
1775
|
+
def linear_transformation(self, linear_transf,
|
|
1776
|
+
new_base_ring=None):
|
|
1777
|
+
"""
|
|
1778
|
+
Return the linear transformation of ``self``.
|
|
1779
|
+
|
|
1780
|
+
INPUT:
|
|
1781
|
+
|
|
1782
|
+
- ``linear_transf`` -- a matrix, not necessarily in :meth:`base_ring`
|
|
1783
|
+
- ``new_base_ring`` -- ring (optional); specify the new base ring;
|
|
1784
|
+
may avoid coercion failure
|
|
1785
|
+
|
|
1786
|
+
OUTPUT:
|
|
1787
|
+
|
|
1788
|
+
The polyhedron transformed by that matrix, possibly coerced to a
|
|
1789
|
+
bigger base ring.
|
|
1790
|
+
|
|
1791
|
+
.. SEEALSO:: :meth:`dilation`, :meth:`translation`
|
|
1792
|
+
|
|
1793
|
+
EXAMPLES::
|
|
1794
|
+
|
|
1795
|
+
sage: b3 = polytopes.Birkhoff_polytope(3)
|
|
1796
|
+
sage: proj_mat = matrix([[0,1,0,0,0,0,0,0,0], [0,0,0,1,0,0,0,0,0],
|
|
1797
|
+
....: [0,0,0,0,0,1,0,0,0], [0,0,0,0,0,0,0,1,0]])
|
|
1798
|
+
sage: b3_proj = proj_mat * b3; b3_proj
|
|
1799
|
+
A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices
|
|
1800
|
+
|
|
1801
|
+
sage: # needs sage.rings.number_field
|
|
1802
|
+
sage: square = polytopes.regular_polygon(4)
|
|
1803
|
+
sage: square.vertices_list()
|
|
1804
|
+
[[0, -1], [1, 0], [-1, 0], [0, 1]]
|
|
1805
|
+
sage: transf = matrix([[1,1], [0,1]])
|
|
1806
|
+
sage: sheared = transf * square
|
|
1807
|
+
sage: sheared.vertices_list()
|
|
1808
|
+
[[-1, -1], [1, 0], [-1, 0], [1, 1]]
|
|
1809
|
+
sage: sheared == square.linear_transformation(transf)
|
|
1810
|
+
True
|
|
1811
|
+
|
|
1812
|
+
Specifying the new base ring may avoid coercion failure::
|
|
1813
|
+
|
|
1814
|
+
sage: # needs sage.rings.number_field
|
|
1815
|
+
sage: K.<sqrt2> = QuadraticField(2)
|
|
1816
|
+
sage: L.<sqrt3> = QuadraticField(3)
|
|
1817
|
+
sage: P = polytopes.cube()*sqrt2
|
|
1818
|
+
sage: M = matrix([[sqrt3, 0, 0], [0, sqrt3, 0], [0, 0, 1]])
|
|
1819
|
+
sage: P.linear_transformation(M, new_base_ring=K.composite_fields(L)[0])
|
|
1820
|
+
A 3-dimensional polyhedron in
|
|
1821
|
+
(Number Field in sqrt2sqrt3 with defining polynomial x^4 - 10*x^2 + 1
|
|
1822
|
+
with sqrt2sqrt3 = 0.3178372451957823?)^3
|
|
1823
|
+
defined as the convex hull of 8 vertices
|
|
1824
|
+
|
|
1825
|
+
Linear transformation without specified new base ring fails in this case::
|
|
1826
|
+
|
|
1827
|
+
sage: M*P # needs sage.rings.number_field
|
|
1828
|
+
Traceback (most recent call last):
|
|
1829
|
+
...
|
|
1830
|
+
TypeError: unsupported operand parent(s) for *:
|
|
1831
|
+
'Full MatrixSpace of 3 by 3 dense matrices over Number Field in sqrt3
|
|
1832
|
+
with defining polynomial x^2 - 3 with sqrt3 = 1.732050807568878?' and
|
|
1833
|
+
'Full MatrixSpace of 3 by 8 dense matrices over Number Field in sqrt2
|
|
1834
|
+
with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095?'
|
|
1835
|
+
|
|
1836
|
+
TESTS:
|
|
1837
|
+
|
|
1838
|
+
One can scale by a scalar as follows::
|
|
1839
|
+
|
|
1840
|
+
sage: P = polytopes.cube()
|
|
1841
|
+
sage: P2 = P.linear_transformation(2); P2
|
|
1842
|
+
A 3-dimensional polyhedron in QQ^3 defined as
|
|
1843
|
+
the convex hull of 8 vertices
|
|
1844
|
+
sage: P2.volume()
|
|
1845
|
+
64
|
|
1846
|
+
|
|
1847
|
+
Linear transformation respects backend::
|
|
1848
|
+
|
|
1849
|
+
sage: P = polytopes.simplex(backend='field')
|
|
1850
|
+
sage: t = matrix([[1,1,1,1],[0,1,1,1],[0,0,1,1],[0,0,0,1]])
|
|
1851
|
+
sage: P.linear_transformation(t).backend()
|
|
1852
|
+
'field'
|
|
1853
|
+
|
|
1854
|
+
Check that coercion works::
|
|
1855
|
+
|
|
1856
|
+
sage: (1.0 * proj_mat) * b3 # needs cddexec
|
|
1857
|
+
A 3-dimensional polyhedron in RDF^4 defined as the convex hull of 5 vertices
|
|
1858
|
+
sage: (1/1 * proj_mat) * b3
|
|
1859
|
+
A 3-dimensional polyhedron in QQ^4 defined as the convex hull of 5 vertices
|
|
1860
|
+
sage: (AA(2).sqrt() * proj_mat) * b3 # needs sage.rings.number_field
|
|
1861
|
+
A 3-dimensional polyhedron in AA^4 defined as the convex hull of 5 vertices
|
|
1862
|
+
|
|
1863
|
+
Check that zero-matrices act correctly::
|
|
1864
|
+
|
|
1865
|
+
sage: Matrix([]) * b3
|
|
1866
|
+
A 0-dimensional polyhedron in ZZ^0 defined as the convex hull of 1 vertex
|
|
1867
|
+
sage: Matrix([[0 for _ in range(9)]]) * b3
|
|
1868
|
+
A 0-dimensional polyhedron in ZZ^1 defined as the convex hull of 1 vertex
|
|
1869
|
+
sage: Matrix([[0 for _ in range(9)] for _ in range(4)]) * b3
|
|
1870
|
+
A 0-dimensional polyhedron in ZZ^4 defined as the convex hull of 1 vertex
|
|
1871
|
+
sage: Matrix([[0 for _ in range(8)]]) * b3
|
|
1872
|
+
Traceback (most recent call last):
|
|
1873
|
+
...
|
|
1874
|
+
TypeError: unsupported operand parent(s) for *:
|
|
1875
|
+
'Full MatrixSpace of 1 by 8 dense matrices over Integer Ring' and
|
|
1876
|
+
'Full MatrixSpace of 9 by 6 dense matrices over Integer Ring'
|
|
1877
|
+
sage: Matrix(ZZ, []) * b3
|
|
1878
|
+
A 0-dimensional polyhedron in ZZ^0 defined as the convex hull of 1 vertex
|
|
1879
|
+
sage: Matrix(ZZ, [[],[]]) * b3
|
|
1880
|
+
Traceback (most recent call last):
|
|
1881
|
+
...
|
|
1882
|
+
TypeError: unsupported operand parent(s) for *:
|
|
1883
|
+
'Full MatrixSpace of 2 by 0 dense matrices over Integer Ring' and
|
|
1884
|
+
'Full MatrixSpace of 9 by 6 dense matrices over Integer Ring'
|
|
1885
|
+
|
|
1886
|
+
Check that the precomputed double description is correct::
|
|
1887
|
+
|
|
1888
|
+
sage: P = polytopes.permutahedron(4)
|
|
1889
|
+
sage: Q = P.change_ring(QQ, backend='field')
|
|
1890
|
+
sage: P.affine_hull_projection() == Q.affine_hull_projection()
|
|
1891
|
+
True
|
|
1892
|
+
|
|
1893
|
+
sage: M = matrix([[1, 2, 3, 4], [2, 3, 4, 5], [0, 0, 5, 1], [0, 2, 0, 3]])
|
|
1894
|
+
sage: M*P == M*Q
|
|
1895
|
+
True
|
|
1896
|
+
|
|
1897
|
+
sage: M = matrix([[1, 2, 3, 4], [2, 3, 4, 5], [0, 0, 5, 1], [0, 2, 0, 3], [0, 1, 0, -3]])
|
|
1898
|
+
sage: M*P == M*Q
|
|
1899
|
+
True
|
|
1900
|
+
"""
|
|
1901
|
+
is_injective = False
|
|
1902
|
+
|
|
1903
|
+
if linear_transf in self.base_ring():
|
|
1904
|
+
# allow for scalar input
|
|
1905
|
+
linear_transf = linear_transf * self.ambient_vector_space().matrix()
|
|
1906
|
+
|
|
1907
|
+
if linear_transf.nrows() != 0:
|
|
1908
|
+
if new_base_ring:
|
|
1909
|
+
R = new_base_ring
|
|
1910
|
+
else:
|
|
1911
|
+
R = self.base_ring()
|
|
1912
|
+
|
|
1913
|
+
# Multiplying a matrix with a vector is slow.
|
|
1914
|
+
# So we multiply the entire vertex matrix etc.
|
|
1915
|
+
# Still we create generators, as possibly the Vrepresentation
|
|
1916
|
+
# will be discarded later on.
|
|
1917
|
+
if self.n_vertices():
|
|
1918
|
+
new_vertices = iter((linear_transf*self.vertices_matrix(R)).transpose())
|
|
1919
|
+
else:
|
|
1920
|
+
new_vertices = ()
|
|
1921
|
+
if self.n_rays():
|
|
1922
|
+
new_rays = iter(matrix(R, self.rays())*linear_transf.transpose())
|
|
1923
|
+
else:
|
|
1924
|
+
new_rays = ()
|
|
1925
|
+
if self.n_lines():
|
|
1926
|
+
new_lines = iter(matrix(R, self.lines())*linear_transf.transpose())
|
|
1927
|
+
else:
|
|
1928
|
+
new_lines = ()
|
|
1929
|
+
|
|
1930
|
+
if self.is_compact() and self.n_vertices() and self.n_inequalities():
|
|
1931
|
+
homogeneous_basis = matrix(R, ( [1] + list(v) for v in self.an_affine_basis() )).transpose()
|
|
1932
|
+
|
|
1933
|
+
# To convert first to a list and then to a matrix seems to be necessary to obtain a meaningful error,
|
|
1934
|
+
# in case the number of columns doesn't match the dimension.
|
|
1935
|
+
new_homogeneous_basis = matrix(list( [1] + list(linear_transf*vector(R, v)) for v in self.an_affine_basis()) ).transpose()
|
|
1936
|
+
|
|
1937
|
+
if self.dim() + 1 == new_homogeneous_basis.rank():
|
|
1938
|
+
# The transformation is injective on the polytope.
|
|
1939
|
+
is_injective = True
|
|
1940
|
+
|
|
1941
|
+
# Let V be the homogeneous vertex matrix (each vertex a column)
|
|
1942
|
+
# and M the linear transformation.
|
|
1943
|
+
# Then M*V is the new homogeneous vertex matrix.
|
|
1944
|
+
|
|
1945
|
+
# Let H be the inequalities matrix (each inequality a row).
|
|
1946
|
+
# If we find N such that N*M*V = V than the new inequalities are
|
|
1947
|
+
# given by H*N.
|
|
1948
|
+
|
|
1949
|
+
# Note that such N must exist, as our map is injective on the polytope.
|
|
1950
|
+
# It is uniquely defined by considering a basis of the homogeneous vertices.
|
|
1951
|
+
N = new_homogeneous_basis.solve_left(homogeneous_basis)
|
|
1952
|
+
new_inequalities = iter(matrix(R, self.inequalities())*N)
|
|
1953
|
+
|
|
1954
|
+
# The equations are the left kernel matrix of the homogeneous vertices
|
|
1955
|
+
# or equivalently a basis thereof.
|
|
1956
|
+
new_equations = (new_homogeneous_basis.transpose()).right_kernel_matrix()
|
|
1957
|
+
|
|
1958
|
+
else:
|
|
1959
|
+
new_vertices = [[] for v in self.vertex_generator()]
|
|
1960
|
+
new_rays = []
|
|
1961
|
+
new_lines = []
|
|
1962
|
+
|
|
1963
|
+
new_dim = linear_transf.nrows()
|
|
1964
|
+
par = self.parent()
|
|
1965
|
+
|
|
1966
|
+
if new_base_ring:
|
|
1967
|
+
new_parent = par.change_ring(new_base_ring, ambient_dim=new_dim)
|
|
1968
|
+
else:
|
|
1969
|
+
new_parent = par.base_extend(linear_transf.base_ring(), ambient_dim=new_dim)
|
|
1970
|
+
|
|
1971
|
+
if is_injective:
|
|
1972
|
+
# Set up with both Vrepresentation and Hrepresentation.
|
|
1973
|
+
pref_rep = 'Vrep' if self.n_vertices() <= self.n_inequalities() else 'Hrep'
|
|
1974
|
+
|
|
1975
|
+
return new_parent.element_class(new_parent, [new_vertices, new_rays, new_lines],
|
|
1976
|
+
[new_inequalities, new_equations],
|
|
1977
|
+
Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep)
|
|
1978
|
+
|
|
1979
|
+
return new_parent.element_class(new_parent, [tuple(new_vertices), tuple(new_rays), tuple(new_lines)], None)
|
|
1980
|
+
|
|
1981
|
+
def _test_linear_transformation(self, tester=None, **options):
|
|
1982
|
+
"""
|
|
1983
|
+
Run some tests on linear transformation.
|
|
1984
|
+
|
|
1985
|
+
TESTS::
|
|
1986
|
+
|
|
1987
|
+
sage: Polyhedron(rays=[(0,1)])._test_linear_transformation()
|
|
1988
|
+
"""
|
|
1989
|
+
if tester is None:
|
|
1990
|
+
tester = self._tester(**options)
|
|
1991
|
+
|
|
1992
|
+
if self.n_vertices() > 200 or self.n_facets() > 200:
|
|
1993
|
+
# Avoid very long doctests.
|
|
1994
|
+
return
|
|
1995
|
+
|
|
1996
|
+
# Check that :issue:`30146` is fixed.
|
|
1997
|
+
from sage.matrix.special import identity_matrix
|
|
1998
|
+
tester.assertEqual(self, self.linear_transformation(identity_matrix(self.ambient_dim())))
|
|
1999
|
+
|
|
2000
|
+
###########################################################
|
|
2001
|
+
# Methods using a face.
|
|
2002
|
+
###########################################################
|
|
2003
|
+
|
|
2004
|
+
def face_truncation(self, face, linear_coefficients=None, cut_frac=None):
|
|
2005
|
+
r"""
|
|
2006
|
+
Return a new polyhedron formed by truncating a face by an hyperplane.
|
|
2007
|
+
|
|
2008
|
+
By default, the normal vector of the hyperplane used to truncate the
|
|
2009
|
+
polyhedron is obtained by taking the barycenter vector of the cone
|
|
2010
|
+
corresponding to the truncated face in the normal fan of the
|
|
2011
|
+
polyhedron. It is possible to change the direction using the option
|
|
2012
|
+
``linear_coefficients``.
|
|
2013
|
+
|
|
2014
|
+
To determine how deep the truncation is done, the method uses the
|
|
2015
|
+
parameter ``cut_frac``. By default it is equal to `\frac{1}{3}`. Once
|
|
2016
|
+
the normal vector of the cutting hyperplane is chosen, the vertices of
|
|
2017
|
+
polyhedron are evaluated according to the corresponding linear
|
|
2018
|
+
function. The parameter `\frac{1}{3}` means that the cutting
|
|
2019
|
+
hyperplane is placed `\frac{1}{3}` of the way from the vertices of the
|
|
2020
|
+
truncated face to the next evaluated vertex.
|
|
2021
|
+
|
|
2022
|
+
INPUT:
|
|
2023
|
+
|
|
2024
|
+
- ``face`` -- a :class:`~sage.geometry.polyhedron.face.PolyhedronFace`
|
|
2025
|
+
- ``linear_coefficients`` -- tuple of integer. Specifies the coefficient
|
|
2026
|
+
of the normal vector of the cutting hyperplane used to truncate the
|
|
2027
|
+
face.
|
|
2028
|
+
The default direction is determined using the normal fan of the
|
|
2029
|
+
polyhedron.
|
|
2030
|
+
- ``cut_frac`` -- number between 0 and 1. Determines where the
|
|
2031
|
+
hyperplane cuts the polyhedron. A value close to 0 cuts very close
|
|
2032
|
+
to the face, whereas a value close to 1 cuts very close to the next
|
|
2033
|
+
vertex (according to the normal vector of the cutting hyperplane).
|
|
2034
|
+
Default is `\frac{1}{3}`.
|
|
2035
|
+
|
|
2036
|
+
OUTPUT: a Polyhedron object, truncated as described above
|
|
2037
|
+
|
|
2038
|
+
EXAMPLES::
|
|
2039
|
+
|
|
2040
|
+
sage: Cube = polytopes.hypercube(3)
|
|
2041
|
+
sage: vertex_trunc1 = Cube.face_truncation(Cube.faces(0)[0])
|
|
2042
|
+
sage: vertex_trunc1.f_vector()
|
|
2043
|
+
(1, 10, 15, 7, 1)
|
|
2044
|
+
sage: tuple(f.ambient_V_indices() for f in vertex_trunc1.faces(2))
|
|
2045
|
+
((4, 5, 6, 7, 9),
|
|
2046
|
+
(0, 3, 4, 8, 9),
|
|
2047
|
+
(0, 1, 6, 7, 8),
|
|
2048
|
+
(7, 8, 9),
|
|
2049
|
+
(2, 3, 4, 5),
|
|
2050
|
+
(1, 2, 5, 6),
|
|
2051
|
+
(0, 1, 2, 3))
|
|
2052
|
+
sage: vertex_trunc1.vertices()
|
|
2053
|
+
(A vertex at (1, -1, -1),
|
|
2054
|
+
A vertex at (1, 1, -1),
|
|
2055
|
+
A vertex at (1, 1, 1),
|
|
2056
|
+
A vertex at (1, -1, 1),
|
|
2057
|
+
A vertex at (-1, -1, 1),
|
|
2058
|
+
A vertex at (-1, 1, 1),
|
|
2059
|
+
A vertex at (-1, 1, -1),
|
|
2060
|
+
A vertex at (-1, -1/3, -1),
|
|
2061
|
+
A vertex at (-1/3, -1, -1),
|
|
2062
|
+
A vertex at (-1, -1, -1/3))
|
|
2063
|
+
sage: vertex_trunc2 = Cube.face_truncation(Cube.faces(0)[0], cut_frac=1/2)
|
|
2064
|
+
sage: vertex_trunc2.f_vector()
|
|
2065
|
+
(1, 10, 15, 7, 1)
|
|
2066
|
+
sage: tuple(f.ambient_V_indices() for f in vertex_trunc2.faces(2))
|
|
2067
|
+
((4, 5, 6, 7, 9),
|
|
2068
|
+
(0, 3, 4, 8, 9),
|
|
2069
|
+
(0, 1, 6, 7, 8),
|
|
2070
|
+
(7, 8, 9),
|
|
2071
|
+
(2, 3, 4, 5),
|
|
2072
|
+
(1, 2, 5, 6),
|
|
2073
|
+
(0, 1, 2, 3))
|
|
2074
|
+
sage: vertex_trunc2.vertices()
|
|
2075
|
+
(A vertex at (1, -1, -1),
|
|
2076
|
+
A vertex at (1, 1, -1),
|
|
2077
|
+
A vertex at (1, 1, 1),
|
|
2078
|
+
A vertex at (1, -1, 1),
|
|
2079
|
+
A vertex at (-1, -1, 1),
|
|
2080
|
+
A vertex at (-1, 1, 1),
|
|
2081
|
+
A vertex at (-1, 1, -1),
|
|
2082
|
+
A vertex at (-1, 0, -1),
|
|
2083
|
+
A vertex at (0, -1, -1),
|
|
2084
|
+
A vertex at (-1, -1, 0))
|
|
2085
|
+
sage: vertex_trunc3 = Cube.face_truncation(Cube.faces(0)[0], cut_frac=0.3) # needs cddexec
|
|
2086
|
+
sage: vertex_trunc3.vertices() # needs cddexec
|
|
2087
|
+
(A vertex at (-1.0, -1.0, 1.0),
|
|
2088
|
+
A vertex at (-1.0, 1.0, -1.0),
|
|
2089
|
+
A vertex at (-1.0, 1.0, 1.0),
|
|
2090
|
+
A vertex at (1.0, 1.0, -1.0),
|
|
2091
|
+
A vertex at (1.0, 1.0, 1.0),
|
|
2092
|
+
A vertex at (1.0, -1.0, 1.0),
|
|
2093
|
+
A vertex at (1.0, -1.0, -1.0),
|
|
2094
|
+
A vertex at (-0.4, -1.0, -1.0),
|
|
2095
|
+
A vertex at (-1.0, -0.4, -1.0),
|
|
2096
|
+
A vertex at (-1.0, -1.0, -0.4))
|
|
2097
|
+
sage: edge_trunc = Cube.face_truncation(Cube.faces(1)[11])
|
|
2098
|
+
sage: edge_trunc.f_vector()
|
|
2099
|
+
(1, 10, 15, 7, 1)
|
|
2100
|
+
sage: tuple(f.ambient_V_indices() for f in edge_trunc.faces(2))
|
|
2101
|
+
((0, 5, 6, 7),
|
|
2102
|
+
(1, 4, 5, 6, 8),
|
|
2103
|
+
(6, 7, 8, 9),
|
|
2104
|
+
(0, 2, 3, 7, 9),
|
|
2105
|
+
(1, 2, 8, 9),
|
|
2106
|
+
(0, 3, 4, 5),
|
|
2107
|
+
(1, 2, 3, 4))
|
|
2108
|
+
sage: face_trunc = Cube.face_truncation(Cube.faces(2)[2])
|
|
2109
|
+
sage: face_trunc.vertices()
|
|
2110
|
+
(A vertex at (1, -1, -1),
|
|
2111
|
+
A vertex at (1, 1, -1),
|
|
2112
|
+
A vertex at (1, 1, 1),
|
|
2113
|
+
A vertex at (1, -1, 1),
|
|
2114
|
+
A vertex at (-1/3, -1, 1),
|
|
2115
|
+
A vertex at (-1/3, 1, 1),
|
|
2116
|
+
A vertex at (-1/3, 1, -1),
|
|
2117
|
+
A vertex at (-1/3, -1, -1))
|
|
2118
|
+
sage: face_trunc.face_lattice().is_isomorphic(Cube.face_lattice()) # needs sage.combinat sage.graphs
|
|
2119
|
+
True
|
|
2120
|
+
|
|
2121
|
+
TESTS:
|
|
2122
|
+
|
|
2123
|
+
Testing that the backend is preserved::
|
|
2124
|
+
|
|
2125
|
+
sage: Cube = polytopes.cube(backend='field')
|
|
2126
|
+
sage: face_trunc = Cube.face_truncation(Cube.faces(2)[0])
|
|
2127
|
+
sage: face_trunc.backend()
|
|
2128
|
+
'field'
|
|
2129
|
+
|
|
2130
|
+
Testing that :issue:`28506` is fixed::
|
|
2131
|
+
|
|
2132
|
+
sage: P = polytopes.twenty_four_cell()
|
|
2133
|
+
sage: P = P.dilation(6)
|
|
2134
|
+
sage: P = P.change_ring(ZZ)
|
|
2135
|
+
sage: P.face_truncation(P.faces(2)[0], cut_frac=1)
|
|
2136
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 27 vertices
|
|
2137
|
+
"""
|
|
2138
|
+
if cut_frac is None:
|
|
2139
|
+
cut_frac = ZZ.one() / 3
|
|
2140
|
+
|
|
2141
|
+
face_vertices = face.vertices()
|
|
2142
|
+
|
|
2143
|
+
normal_vectors = []
|
|
2144
|
+
|
|
2145
|
+
for facet in self.Hrepresentation():
|
|
2146
|
+
if all(facet.contains(x) and not facet.interior_contains(x)
|
|
2147
|
+
for x in face_vertices):
|
|
2148
|
+
# The facet contains the face
|
|
2149
|
+
normal_vectors.append(facet.A())
|
|
2150
|
+
|
|
2151
|
+
if linear_coefficients is not None:
|
|
2152
|
+
normal_vector = sum(linear_coefficients[i]*normal_vectors[i]
|
|
2153
|
+
for i in range(len(normal_vectors)))
|
|
2154
|
+
else:
|
|
2155
|
+
normal_vector = sum(normal_vectors)
|
|
2156
|
+
|
|
2157
|
+
B = - normal_vector * (face_vertices[0].vector())
|
|
2158
|
+
|
|
2159
|
+
linear_evaluation = set(-normal_vector * (v.vector()) for v in self.vertices())
|
|
2160
|
+
|
|
2161
|
+
if B == max(linear_evaluation):
|
|
2162
|
+
C = max(linear_evaluation.difference(set([B])))
|
|
2163
|
+
else:
|
|
2164
|
+
C = min(linear_evaluation.difference(set([B])))
|
|
2165
|
+
|
|
2166
|
+
cut_height = (1 - cut_frac) * B + cut_frac * C
|
|
2167
|
+
ineq_vector = tuple([cut_height]) + tuple(normal_vector)
|
|
2168
|
+
|
|
2169
|
+
new_ieqs = self.inequalities_list() + [ineq_vector]
|
|
2170
|
+
new_eqns = self.equations_list()
|
|
2171
|
+
|
|
2172
|
+
# Some vertices might need fractions.
|
|
2173
|
+
parent = self.parent().base_extend(cut_frac/1)
|
|
2174
|
+
return parent.element_class(parent, None, [new_ieqs, new_eqns])
|
|
2175
|
+
|
|
2176
|
+
def stack(self, face, position=None):
|
|
2177
|
+
r"""
|
|
2178
|
+
Return a new polyhedron formed by stacking onto a ``face``. Stacking a
|
|
2179
|
+
face adds a new vertex located slightly outside of the designated face.
|
|
2180
|
+
|
|
2181
|
+
INPUT:
|
|
2182
|
+
|
|
2183
|
+
- ``face`` -- a PolyhedronFace
|
|
2184
|
+
|
|
2185
|
+
- ``position`` -- a positive number. Determines a relative distance
|
|
2186
|
+
from the barycenter of ``face``. A value close to 0 will place the
|
|
2187
|
+
new vertex close to the face and a large value further away. Default
|
|
2188
|
+
is `1`. If the given value is too large, an error is returned.
|
|
2189
|
+
|
|
2190
|
+
OUTPUT: a Polyhedron object
|
|
2191
|
+
|
|
2192
|
+
EXAMPLES::
|
|
2193
|
+
|
|
2194
|
+
sage: cube = polytopes.cube()
|
|
2195
|
+
sage: square_face = cube.facets()[2]
|
|
2196
|
+
sage: stacked_square = cube.stack(square_face)
|
|
2197
|
+
sage: stacked_square.f_vector()
|
|
2198
|
+
(1, 9, 16, 9, 1)
|
|
2199
|
+
|
|
2200
|
+
sage: edge_face = cube.faces(1)[3]
|
|
2201
|
+
sage: stacked_edge = cube.stack(edge_face)
|
|
2202
|
+
sage: stacked_edge.f_vector()
|
|
2203
|
+
(1, 9, 17, 10, 1)
|
|
2204
|
+
|
|
2205
|
+
sage: cube.stack(cube.faces(0)[0])
|
|
2206
|
+
Traceback (most recent call last):
|
|
2207
|
+
...
|
|
2208
|
+
ValueError: cannot stack onto a vertex
|
|
2209
|
+
|
|
2210
|
+
sage: stacked_square_half = cube.stack(square_face, position=1/2)
|
|
2211
|
+
sage: stacked_square_half.f_vector()
|
|
2212
|
+
(1, 9, 16, 9, 1)
|
|
2213
|
+
sage: stacked_square_large = cube.stack(square_face, position=10)
|
|
2214
|
+
|
|
2215
|
+
sage: # needs sage.rings.number_field
|
|
2216
|
+
sage: hexaprism = polytopes.regular_polygon(6).prism()
|
|
2217
|
+
sage: hexaprism.f_vector()
|
|
2218
|
+
(1, 12, 18, 8, 1)
|
|
2219
|
+
sage: square_face = hexaprism.faces(2)[2]
|
|
2220
|
+
sage: stacked_hexaprism = hexaprism.stack(square_face)
|
|
2221
|
+
sage: stacked_hexaprism.f_vector()
|
|
2222
|
+
(1, 13, 22, 11, 1)
|
|
2223
|
+
|
|
2224
|
+
sage: hexaprism.stack(square_face, position=4) # needs sage.rings.number_field
|
|
2225
|
+
Traceback (most recent call last):
|
|
2226
|
+
...
|
|
2227
|
+
ValueError: the chosen position is too large
|
|
2228
|
+
|
|
2229
|
+
sage: s = polytopes.simplex(7)
|
|
2230
|
+
sage: f = s.faces(3)[69]
|
|
2231
|
+
sage: sf = s.stack(f); sf
|
|
2232
|
+
A 7-dimensional polyhedron in QQ^8 defined as the convex hull of 9 vertices
|
|
2233
|
+
sage: sf.vertices()
|
|
2234
|
+
(A vertex at (-4, -4, -4, -4, 17/4, 17/4, 17/4, 17/4),
|
|
2235
|
+
A vertex at (0, 0, 0, 0, 0, 0, 0, 1),
|
|
2236
|
+
A vertex at (0, 0, 0, 0, 0, 0, 1, 0),
|
|
2237
|
+
A vertex at (0, 0, 0, 0, 0, 1, 0, 0),
|
|
2238
|
+
A vertex at (0, 0, 0, 0, 1, 0, 0, 0),
|
|
2239
|
+
A vertex at (0, 0, 0, 1, 0, 0, 0, 0),
|
|
2240
|
+
A vertex at (0, 0, 1, 0, 0, 0, 0, 0),
|
|
2241
|
+
A vertex at (0, 1, 0, 0, 0, 0, 0, 0),
|
|
2242
|
+
A vertex at (1, 0, 0, 0, 0, 0, 0, 0))
|
|
2243
|
+
|
|
2244
|
+
It is possible to stack on unbounded faces::
|
|
2245
|
+
|
|
2246
|
+
sage: Q = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,1]])
|
|
2247
|
+
sage: E = Q.faces(1)
|
|
2248
|
+
sage: Q.stack(E[0],1/2).Vrepresentation()
|
|
2249
|
+
(A vertex at (0, 1),
|
|
2250
|
+
A vertex at (1, 0),
|
|
2251
|
+
A ray in the direction (1, 1),
|
|
2252
|
+
A vertex at (2, 0))
|
|
2253
|
+
sage: Q.stack(E[1],1/2).Vrepresentation()
|
|
2254
|
+
(A vertex at (0, 1),
|
|
2255
|
+
A vertex at (0, 2),
|
|
2256
|
+
A vertex at (1, 0),
|
|
2257
|
+
A ray in the direction (1, 1))
|
|
2258
|
+
sage: Q.stack(E[2],1/2).Vrepresentation()
|
|
2259
|
+
(A vertex at (0, 0),
|
|
2260
|
+
A vertex at (0, 1),
|
|
2261
|
+
A vertex at (1, 0),
|
|
2262
|
+
A ray in the direction (1, 1))
|
|
2263
|
+
|
|
2264
|
+
Stacking requires a proper face::
|
|
2265
|
+
|
|
2266
|
+
sage: Q.stack(Q.faces(2)[0])
|
|
2267
|
+
Traceback (most recent call last):
|
|
2268
|
+
...
|
|
2269
|
+
ValueError: can only stack on proper face
|
|
2270
|
+
|
|
2271
|
+
TESTS:
|
|
2272
|
+
|
|
2273
|
+
Checking that the backend is preserved::
|
|
2274
|
+
|
|
2275
|
+
sage: Cube = polytopes.cube(backend='field')
|
|
2276
|
+
sage: stack = Cube.stack(Cube.faces(2)[0])
|
|
2277
|
+
sage: stack.backend()
|
|
2278
|
+
'field'
|
|
2279
|
+
|
|
2280
|
+
Taking the stacking vertex too far with the parameter ``position``
|
|
2281
|
+
may result in a failure to produce the desired
|
|
2282
|
+
(combinatorial type of) polytope.
|
|
2283
|
+
The interval of permitted values is always open.
|
|
2284
|
+
This is the smallest unpermitted value::
|
|
2285
|
+
|
|
2286
|
+
sage: P = polytopes.octahedron()
|
|
2287
|
+
sage: P.stack(P.faces(2)[0], position=4)
|
|
2288
|
+
Traceback (most recent call last):
|
|
2289
|
+
...
|
|
2290
|
+
ValueError: the chosen position is too large
|
|
2291
|
+
|
|
2292
|
+
Testing that :issue:`29057` is fixed::
|
|
2293
|
+
|
|
2294
|
+
sage: P = polytopes.cross_polytope(4)
|
|
2295
|
+
sage: P.stack(P.faces(3)[0])
|
|
2296
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 9 vertices
|
|
2297
|
+
"""
|
|
2298
|
+
from sage.geometry.polyhedron.face import PolyhedronFace
|
|
2299
|
+
if not isinstance(face, PolyhedronFace):
|
|
2300
|
+
raise TypeError("{} should be a PolyhedronFace of {}".format(face, self))
|
|
2301
|
+
elif face.dim() == 0:
|
|
2302
|
+
raise ValueError("cannot stack onto a vertex")
|
|
2303
|
+
elif face.dim() == -1 or face.dim() == self.dim():
|
|
2304
|
+
raise ValueError("can only stack on proper face")
|
|
2305
|
+
if position is None:
|
|
2306
|
+
position = 1
|
|
2307
|
+
|
|
2308
|
+
barycenter = ZZ.one()*sum([v.vector() for v in face.vertices()]) / len(face.vertices())
|
|
2309
|
+
locus_polyhedron = face.stacking_locus()
|
|
2310
|
+
repr_point = locus_polyhedron.representative_point()
|
|
2311
|
+
new_vertex = (1-position)*barycenter + position*repr_point
|
|
2312
|
+
if not locus_polyhedron.relative_interior_contains(new_vertex):
|
|
2313
|
+
raise ValueError("the chosen position is too large")
|
|
2314
|
+
|
|
2315
|
+
parent = self.parent().base_extend(new_vertex)
|
|
2316
|
+
return parent.element_class(parent, [self.vertices() + (new_vertex,), self.rays(), self.lines()], None)
|
|
2317
|
+
|
|
2318
|
+
def wedge(self, face, width=1):
|
|
2319
|
+
r"""
|
|
2320
|
+
Return the wedge over a ``face`` of the polytope ``self``.
|
|
2321
|
+
|
|
2322
|
+
The wedge over a face `F` of a polytope `P` with width `w \not= 0`
|
|
2323
|
+
is defined as:
|
|
2324
|
+
|
|
2325
|
+
.. MATH::
|
|
2326
|
+
|
|
2327
|
+
(P \times \mathbb{R}) \cap \{a^\top x + |w x_{d+1}| \leq b\}
|
|
2328
|
+
|
|
2329
|
+
where `\{x | a^\top x = b\}` is a supporting hyperplane defining `F`.
|
|
2330
|
+
|
|
2331
|
+
INPUT:
|
|
2332
|
+
|
|
2333
|
+
- ``face`` -- a PolyhedronFace of ``self``, the face which we take
|
|
2334
|
+
the wedge over
|
|
2335
|
+
- ``width`` -- a nonzero number (default: ``1``);
|
|
2336
|
+
specifies how wide the wedge will be
|
|
2337
|
+
|
|
2338
|
+
OUTPUT:
|
|
2339
|
+
|
|
2340
|
+
A (bounded) polyhedron
|
|
2341
|
+
|
|
2342
|
+
EXAMPLES::
|
|
2343
|
+
|
|
2344
|
+
sage: # needs sage.rings.number_field
|
|
2345
|
+
sage: P_4 = polytopes.regular_polygon(4)
|
|
2346
|
+
sage: W1 = P_4.wedge(P_4.faces(1)[0]); W1
|
|
2347
|
+
A 3-dimensional polyhedron in AA^3 defined as the convex hull of 6 vertices
|
|
2348
|
+
sage: triangular_prism = polytopes.regular_polygon(3).prism()
|
|
2349
|
+
sage: W1.is_combinatorially_isomorphic(triangular_prism) # needs sage.graphs
|
|
2350
|
+
True
|
|
2351
|
+
|
|
2352
|
+
sage: Q = polytopes.hypersimplex(4,2)
|
|
2353
|
+
sage: W2 = Q.wedge(Q.faces(2)[7]); W2
|
|
2354
|
+
A 4-dimensional polyhedron in QQ^5 defined as the convex hull of 9 vertices
|
|
2355
|
+
sage: W2.vertices()
|
|
2356
|
+
(A vertex at (1, 1, 0, 0, 1),
|
|
2357
|
+
A vertex at (1, 1, 0, 0, -1),
|
|
2358
|
+
A vertex at (1, 0, 1, 0, 1),
|
|
2359
|
+
A vertex at (1, 0, 1, 0, -1),
|
|
2360
|
+
A vertex at (1, 0, 0, 1, 1),
|
|
2361
|
+
A vertex at (1, 0, 0, 1, -1),
|
|
2362
|
+
A vertex at (0, 0, 1, 1, 0),
|
|
2363
|
+
A vertex at (0, 1, 1, 0, 0),
|
|
2364
|
+
A vertex at (0, 1, 0, 1, 0))
|
|
2365
|
+
|
|
2366
|
+
sage: W3 = Q.wedge(Q.faces(1)[11]); W3
|
|
2367
|
+
A 4-dimensional polyhedron in QQ^5 defined as the convex hull of 10 vertices
|
|
2368
|
+
sage: W3.vertices()
|
|
2369
|
+
(A vertex at (1, 1, 0, 0, -2),
|
|
2370
|
+
A vertex at (1, 1, 0, 0, 2),
|
|
2371
|
+
A vertex at (1, 0, 1, 0, -2),
|
|
2372
|
+
A vertex at (1, 0, 1, 0, 2),
|
|
2373
|
+
A vertex at (1, 0, 0, 1, 1),
|
|
2374
|
+
A vertex at (1, 0, 0, 1, -1),
|
|
2375
|
+
A vertex at (0, 1, 0, 1, 0),
|
|
2376
|
+
A vertex at (0, 1, 1, 0, 1),
|
|
2377
|
+
A vertex at (0, 0, 1, 1, 0),
|
|
2378
|
+
A vertex at (0, 1, 1, 0, -1))
|
|
2379
|
+
|
|
2380
|
+
sage: C_3_7 = polytopes.cyclic_polytope(3,7)
|
|
2381
|
+
sage: P_6 = polytopes.regular_polygon(6) # needs sage.rings.number_field
|
|
2382
|
+
sage: W4 = P_6.wedge(P_6.faces(1)[0]) # needs sage.rings.number_field
|
|
2383
|
+
sage: W4.is_combinatorially_isomorphic(C_3_7.polar()) # needs sage.graphs sage.rings.number_field
|
|
2384
|
+
True
|
|
2385
|
+
|
|
2386
|
+
REFERENCES:
|
|
2387
|
+
|
|
2388
|
+
For more information, see Chapter 15 of [HoDaCG17]_.
|
|
2389
|
+
|
|
2390
|
+
TESTS:
|
|
2391
|
+
|
|
2392
|
+
The backend should be preserved as long as the value of width permits.
|
|
2393
|
+
The base_ring will change to the field of fractions of the current
|
|
2394
|
+
base_ring, unless ``width`` forces a different ring. ::
|
|
2395
|
+
|
|
2396
|
+
sage: P = polytopes.cyclic_polytope(3,7, base_ring=ZZ, backend='field')
|
|
2397
|
+
sage: W1 = P.wedge(P.faces(2)[0]); W1.base_ring(); W1.backend()
|
|
2398
|
+
Rational Field
|
|
2399
|
+
'field'
|
|
2400
|
+
sage: W2 = P.wedge(P.faces(2)[0], width=5/2); W2.base_ring(); W2.backend()
|
|
2401
|
+
Rational Field
|
|
2402
|
+
'field'
|
|
2403
|
+
sage: W2 = P.wedge(P.faces(2)[9], width=4/2); W2.base_ring(); W2.backend()
|
|
2404
|
+
Rational Field
|
|
2405
|
+
'field'
|
|
2406
|
+
sage: W2.vertices()
|
|
2407
|
+
(A vertex at (3, 9, 27, -1/2),
|
|
2408
|
+
A vertex at (4, 16, 64, -2),
|
|
2409
|
+
A vertex at (6, 36, 216, -10),
|
|
2410
|
+
A vertex at (5, 25, 125, -5),
|
|
2411
|
+
A vertex at (2, 4, 8, 0),
|
|
2412
|
+
A vertex at (1, 1, 1, 0),
|
|
2413
|
+
A vertex at (0, 0, 0, 0),
|
|
2414
|
+
A vertex at (3, 9, 27, 1/2),
|
|
2415
|
+
A vertex at (4, 16, 64, 2),
|
|
2416
|
+
A vertex at (6, 36, 216, 10),
|
|
2417
|
+
A vertex at (5, 25, 125, 5))
|
|
2418
|
+
sage: W2 = P.wedge(P.faces(2)[2], width=1.0); W2.base_ring(); W2.backend() # needs cddexec
|
|
2419
|
+
Real Double Field
|
|
2420
|
+
'cdd'
|
|
2421
|
+
"""
|
|
2422
|
+
width = width*ZZ.one()
|
|
2423
|
+
|
|
2424
|
+
if not self.is_compact():
|
|
2425
|
+
raise ValueError("polyhedron 'self' must be a polytope")
|
|
2426
|
+
|
|
2427
|
+
if width == 0:
|
|
2428
|
+
raise ValueError("the width should be nonzero")
|
|
2429
|
+
|
|
2430
|
+
from sage.geometry.polyhedron.face import PolyhedronFace
|
|
2431
|
+
if not isinstance(face, PolyhedronFace):
|
|
2432
|
+
raise TypeError("{} should be a PolyhedronFace of {}".format(face, self))
|
|
2433
|
+
|
|
2434
|
+
F_Hrep = vector([0]*(self.ambient_dim()+1))
|
|
2435
|
+
for facet in face.ambient_Hrepresentation():
|
|
2436
|
+
if facet.is_inequality():
|
|
2437
|
+
F_Hrep = F_Hrep + facet.vector()
|
|
2438
|
+
F_Hrep = list(F_Hrep)
|
|
2439
|
+
|
|
2440
|
+
parent = self.parent()
|
|
2441
|
+
parent1 = parent.base_extend(self.base_ring(), ambient_dim=1)
|
|
2442
|
+
parent2 = parent.base_extend(width.base_ring().fraction_field(), ambient_dim=1 + self.ambient_dim())
|
|
2443
|
+
|
|
2444
|
+
L = parent1.element_class(parent1, [[[0]], [], [[1]]], None)
|
|
2445
|
+
Q = self.product(L)
|
|
2446
|
+
ieqs = [F_Hrep + [width], F_Hrep + [-width]]
|
|
2447
|
+
|
|
2448
|
+
H = parent2.element_class(parent2, None, [ieqs, []])
|
|
2449
|
+
return Q.intersection(H)
|
|
2450
|
+
|
|
2451
|
+
def face_split(self, face):
|
|
2452
|
+
"""
|
|
2453
|
+
Return the face splitting of the face ``face``.
|
|
2454
|
+
|
|
2455
|
+
Splitting a face correspond to the bipyramid (see :meth:`bipyramid`)
|
|
2456
|
+
of ``self`` where the two new vertices are placed above and below
|
|
2457
|
+
the center of ``face`` instead of the center of the whole polyhedron.
|
|
2458
|
+
The two new vertices are placed in the new dimension at height `-1` and
|
|
2459
|
+
`1`.
|
|
2460
|
+
|
|
2461
|
+
INPUT:
|
|
2462
|
+
|
|
2463
|
+
- ``face`` -- a PolyhedronFace or a Vertex
|
|
2464
|
+
|
|
2465
|
+
EXAMPLES::
|
|
2466
|
+
|
|
2467
|
+
sage: # needs sage.rings.number_field
|
|
2468
|
+
sage: pentagon = polytopes.regular_polygon(5)
|
|
2469
|
+
sage: f = pentagon.faces(1)[0]
|
|
2470
|
+
sage: fsplit_pentagon = pentagon.face_split(f)
|
|
2471
|
+
sage: fsplit_pentagon.f_vector()
|
|
2472
|
+
(1, 7, 14, 9, 1)
|
|
2473
|
+
|
|
2474
|
+
TESTS:
|
|
2475
|
+
|
|
2476
|
+
Check that :issue:`28668` is fixed::
|
|
2477
|
+
|
|
2478
|
+
sage: P = polytopes.octahedron()
|
|
2479
|
+
sage: P.face_split(P.faces(2)[0])
|
|
2480
|
+
A 4-dimensional polyhedron in QQ^4 defined as the convex hull of 8 vertices
|
|
2481
|
+
|
|
2482
|
+
.. SEEALSO::
|
|
2483
|
+
|
|
2484
|
+
:meth:`one_point_suspension`
|
|
2485
|
+
"""
|
|
2486
|
+
from sage.geometry.polyhedron.representation import Vertex
|
|
2487
|
+
from sage.geometry.polyhedron.face import PolyhedronFace
|
|
2488
|
+
if isinstance(face, Vertex):
|
|
2489
|
+
new_vertices = [list(x) + [0] for x in self.vertex_generator()] + \
|
|
2490
|
+
[list(face) + [x] for x in [-1, 1]] # Splitting the vertex
|
|
2491
|
+
elif isinstance(face, PolyhedronFace):
|
|
2492
|
+
new_vertices = [list(x) + [0] for x in self.vertex_generator()] + \
|
|
2493
|
+
[list(face.as_polyhedron().center()) + [x] for x in [-1, 1]] # Splitting the face
|
|
2494
|
+
else:
|
|
2495
|
+
raise TypeError("the face {} should be a Vertex or PolyhedronFace".format(face))
|
|
2496
|
+
|
|
2497
|
+
new_rays = []
|
|
2498
|
+
new_rays.extend( [ r + [0] for r in self.ray_generator() ] )
|
|
2499
|
+
|
|
2500
|
+
new_lines = []
|
|
2501
|
+
new_lines.extend( [ l + [0] for l in self.line_generator() ] )
|
|
2502
|
+
|
|
2503
|
+
parent = self.parent().change_ring(self.base_ring().fraction_field(), ambient_dim=self.ambient_dim()+1)
|
|
2504
|
+
return parent.element_class(parent, [new_vertices, new_rays, new_lines], None)
|
|
2505
|
+
|
|
2506
|
+
###########################################################
|
|
2507
|
+
# Methods using a vertex or vector.
|
|
2508
|
+
###########################################################
|
|
2509
|
+
|
|
2510
|
+
def lawrence_extension(self, v):
|
|
2511
|
+
"""
|
|
2512
|
+
Return the Lawrence extension of ``self`` on the point ``v``.
|
|
2513
|
+
|
|
2514
|
+
Let `P` be a polytope and `v` be a vertex of `P` or a point outside
|
|
2515
|
+
`P`. The Lawrence extension of `P` on `v` is the convex hull of
|
|
2516
|
+
`(v,1),(v,2)` and `(u,0)` for all vertices `u` in `P` other than `v`
|
|
2517
|
+
if `v` is a vertex.
|
|
2518
|
+
|
|
2519
|
+
INPUT:
|
|
2520
|
+
|
|
2521
|
+
- ``v`` -- a vertex of ``self`` or a point outside it
|
|
2522
|
+
|
|
2523
|
+
EXAMPLES::
|
|
2524
|
+
|
|
2525
|
+
sage: P = polytopes.cube()
|
|
2526
|
+
sage: P.lawrence_extension(P.vertices()[0])
|
|
2527
|
+
A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 9 vertices
|
|
2528
|
+
sage: P.lawrence_extension([-1,-1,-1])
|
|
2529
|
+
A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 9 vertices
|
|
2530
|
+
|
|
2531
|
+
REFERENCES:
|
|
2532
|
+
|
|
2533
|
+
For more information, see Section 6.6 of [Zie2007]_.
|
|
2534
|
+
"""
|
|
2535
|
+
if not self.is_compact():
|
|
2536
|
+
raise NotImplementedError("self must be a polytope")
|
|
2537
|
+
|
|
2538
|
+
V = self.vertices_list()
|
|
2539
|
+
v = list(v)
|
|
2540
|
+
|
|
2541
|
+
if self.contains(v) and (v not in V):
|
|
2542
|
+
raise ValueError("{} must not be a vertex or outside self".format(v))
|
|
2543
|
+
|
|
2544
|
+
lambda_V = [u + [0] for u in V if u != v] + [v+[1]] + [v+[2]]
|
|
2545
|
+
parent = self.parent().base_extend(vector(v), ambient_dim=self.ambient_dim() + 1)
|
|
2546
|
+
return parent.element_class(parent, [lambda_V, [], []], None)
|
|
2547
|
+
|
|
2548
|
+
def _test_lawrence(self, tester=None, **options):
|
|
2549
|
+
"""
|
|
2550
|
+
Run tests on the methods related to lawrence extensions.
|
|
2551
|
+
|
|
2552
|
+
TESTS:
|
|
2553
|
+
|
|
2554
|
+
Check that :issue:`28725` is fixed::
|
|
2555
|
+
|
|
2556
|
+
sage: polytopes.regular_polygon(3)._test_lawrence() # needs sage.rings.number_field
|
|
2557
|
+
|
|
2558
|
+
Check that :issue:`30293` is fixed::
|
|
2559
|
+
|
|
2560
|
+
sage: polytopes.cube()._test_lawrence()
|
|
2561
|
+
"""
|
|
2562
|
+
if tester is None:
|
|
2563
|
+
tester = self._tester(**options)
|
|
2564
|
+
|
|
2565
|
+
if self.backend() == 'normaliz' and self.base_ring() not in (ZZ, QQ):
|
|
2566
|
+
# Speeds up the doctest for significantly.
|
|
2567
|
+
self = self.change_ring(self._internal_base_ring)
|
|
2568
|
+
|
|
2569
|
+
if not self.is_compact():
|
|
2570
|
+
with tester.assertRaises(NotImplementedError):
|
|
2571
|
+
self.lawrence_polytope()
|
|
2572
|
+
with tester.assertRaises(NotImplementedError):
|
|
2573
|
+
self.lawrence_extension(self.vertices()[0])
|
|
2574
|
+
return
|
|
2575
|
+
|
|
2576
|
+
if self.n_vertices() > 1:
|
|
2577
|
+
# ``v`` must be a vertex or outside ``self``.
|
|
2578
|
+
with tester.assertRaises(ValueError):
|
|
2579
|
+
self.lawrence_extension(self.center())
|
|
2580
|
+
|
|
2581
|
+
if self.n_vertices() >= 40 or self.n_facets() > 40:
|
|
2582
|
+
# Avoid very long tests.
|
|
2583
|
+
return
|
|
2584
|
+
|
|
2585
|
+
if self.n_vertices():
|
|
2586
|
+
from sage.misc.prandom import randint
|
|
2587
|
+
v = self.vertices()[randint(0, self.n_vertices()-1)].vector()
|
|
2588
|
+
|
|
2589
|
+
# A lawrence extension with a vertex.
|
|
2590
|
+
P = self.lawrence_extension(v)
|
|
2591
|
+
tester.assertEqual(self.dim() + 1, P.dim())
|
|
2592
|
+
tester.assertEqual(self.n_vertices() + 1, P.n_vertices())
|
|
2593
|
+
tester.assertEqual(self.backend(), P.backend())
|
|
2594
|
+
|
|
2595
|
+
if self.n_vertices() > 1:
|
|
2596
|
+
# A lawrence extension with a point outside of the polyhedron.
|
|
2597
|
+
Q = self.lawrence_extension(2*v - self.center())
|
|
2598
|
+
tester.assertEqual(self.dim() + 1, Q.dim())
|
|
2599
|
+
tester.assertEqual(self.n_vertices() + 2, Q.n_vertices())
|
|
2600
|
+
tester.assertEqual(self.backend(), Q.backend()) # Any backend should handle the fraction field.
|
|
2601
|
+
|
|
2602
|
+
import warnings
|
|
2603
|
+
|
|
2604
|
+
with warnings.catch_warnings():
|
|
2605
|
+
warnings.simplefilter("error")
|
|
2606
|
+
try:
|
|
2607
|
+
from sage.rings.real_double_field import RDF
|
|
2608
|
+
two = RDF(2.0)
|
|
2609
|
+
# Implicitly checks :issue:`30328`.
|
|
2610
|
+
R = self.lawrence_extension(two * v - self.center())
|
|
2611
|
+
tester.assertEqual(self.dim() + 1, R.dim())
|
|
2612
|
+
tester.assertEqual(self.n_vertices() + 2, R.n_vertices())
|
|
2613
|
+
|
|
2614
|
+
tester.assertTrue(Q.is_combinatorially_isomorphic(R))
|
|
2615
|
+
except ImportError:
|
|
2616
|
+
# RDF not available
|
|
2617
|
+
pass
|
|
2618
|
+
except UserWarning:
|
|
2619
|
+
# Data is numerically complicated.
|
|
2620
|
+
pass
|
|
2621
|
+
except ValueError as err:
|
|
2622
|
+
if "Numerical inconsistency" not in err.args[0]:
|
|
2623
|
+
raise err
|
|
2624
|
+
|
|
2625
|
+
if self.n_vertices() >= 12 or (self.base_ring() not in (ZZ, QQ) and self.backend() == 'field'):
|
|
2626
|
+
# Avoid very long tests.
|
|
2627
|
+
return
|
|
2628
|
+
|
|
2629
|
+
P = self.lawrence_polytope()
|
|
2630
|
+
tester.assertEqual(self.dim() + self.n_vertices(), P.dim())
|
|
2631
|
+
tester.assertEqual(self.n_vertices()*2, P.n_vertices())
|
|
2632
|
+
tester.assertEqual(self.backend(), P.backend())
|
|
2633
|
+
tester.assertTrue(P.is_lawrence_polytope())
|
|
2634
|
+
|
|
2635
|
+
# Construct the lawrence polytope iteratively by lawrence extensions.
|
|
2636
|
+
V = self.vertices_list()
|
|
2637
|
+
Q = self
|
|
2638
|
+
i = 0
|
|
2639
|
+
for v in V:
|
|
2640
|
+
v = v + i*[0]
|
|
2641
|
+
Q = Q.lawrence_extension(v)
|
|
2642
|
+
i = i + 1
|
|
2643
|
+
tester.assertEqual(P, Q)
|
|
2644
|
+
|
|
2645
|
+
def one_point_suspension(self, vertex):
|
|
2646
|
+
"""
|
|
2647
|
+
Return the one-point suspension of ``self`` by splitting the vertex
|
|
2648
|
+
``vertex``.
|
|
2649
|
+
|
|
2650
|
+
The resulting polyhedron has one more vertex and its dimension
|
|
2651
|
+
increases by one.
|
|
2652
|
+
|
|
2653
|
+
INPUT:
|
|
2654
|
+
|
|
2655
|
+
- ``vertex`` -- a Vertex of ``self``
|
|
2656
|
+
|
|
2657
|
+
EXAMPLES::
|
|
2658
|
+
|
|
2659
|
+
sage: cube = polytopes.cube()
|
|
2660
|
+
sage: v = cube.vertices()[0]
|
|
2661
|
+
sage: ops_cube = cube.one_point_suspension(v)
|
|
2662
|
+
sage: ops_cube.f_vector()
|
|
2663
|
+
(1, 9, 24, 24, 9, 1)
|
|
2664
|
+
|
|
2665
|
+
sage: # needs sage.rings.number_field
|
|
2666
|
+
sage: pentagon = polytopes.regular_polygon(5)
|
|
2667
|
+
sage: v = pentagon.vertices()[0]
|
|
2668
|
+
sage: ops_pentagon = pentagon.one_point_suspension(v)
|
|
2669
|
+
sage: ops_pentagon.f_vector()
|
|
2670
|
+
(1, 6, 12, 8, 1)
|
|
2671
|
+
|
|
2672
|
+
It works with a polyhedral face as well::
|
|
2673
|
+
|
|
2674
|
+
sage: vv = cube.faces(0)[1]
|
|
2675
|
+
sage: ops_cube2 = cube.one_point_suspension(vv)
|
|
2676
|
+
sage: ops_cube == ops_cube2
|
|
2677
|
+
True
|
|
2678
|
+
|
|
2679
|
+
.. SEEALSO::
|
|
2680
|
+
|
|
2681
|
+
:meth:`face_split`
|
|
2682
|
+
|
|
2683
|
+
TESTS::
|
|
2684
|
+
|
|
2685
|
+
sage: e = cube.faces(1)[0]
|
|
2686
|
+
sage: cube.one_point_suspension(e)
|
|
2687
|
+
Traceback (most recent call last):
|
|
2688
|
+
...
|
|
2689
|
+
TypeError: the vertex
|
|
2690
|
+
A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices
|
|
2691
|
+
should be a Vertex or PolyhedronFace of dimension 0
|
|
2692
|
+
"""
|
|
2693
|
+
from sage.geometry.polyhedron.representation import Vertex
|
|
2694
|
+
from sage.geometry.polyhedron.face import PolyhedronFace
|
|
2695
|
+
if isinstance(vertex, Vertex):
|
|
2696
|
+
return self.face_split(vertex)
|
|
2697
|
+
elif isinstance(vertex, PolyhedronFace) and vertex.dim() == 0:
|
|
2698
|
+
return self.face_split(vertex)
|
|
2699
|
+
else:
|
|
2700
|
+
raise TypeError("the vertex {} should be a Vertex or PolyhedronFace of dimension 0".format(vertex))
|