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,556 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-polyhedra
|
|
2
|
+
"""
|
|
3
|
+
Fast Lattice Polygons using PPL
|
|
4
|
+
|
|
5
|
+
See :mod:`ppl_lattice_polytope` for the implementation of
|
|
6
|
+
arbitrary-dimensional lattice polytopes. This module is about the
|
|
7
|
+
specialization to 2 dimensions. To be more precise, the
|
|
8
|
+
:class:`LatticePolygon_PPL_class` is used if the ambient space is of
|
|
9
|
+
dimension 2 or less. These all allow you to cyclically order (see
|
|
10
|
+
:meth:`LatticePolygon_PPL_class.ordered_vertices`) the vertices, which
|
|
11
|
+
is in general not possible in higher dimensions.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
########################################################################
|
|
15
|
+
# Copyright (C) 2012 Volker Braun <vbraun.name@gmail.com>
|
|
16
|
+
#
|
|
17
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
18
|
+
#
|
|
19
|
+
# http://www.gnu.org/licenses/
|
|
20
|
+
########################################################################
|
|
21
|
+
|
|
22
|
+
from sage.rings.integer_ring import ZZ
|
|
23
|
+
from sage.misc.cachefunc import cached_method, cached_function
|
|
24
|
+
from sage.modules.free_module_element import vector, zero_vector
|
|
25
|
+
from sage.matrix.constructor import (matrix, zero_matrix, block_matrix)
|
|
26
|
+
from ppl import C_Polyhedron, Poly_Con_Relation
|
|
27
|
+
from sage.geometry.polyhedron.lattice_euclidean_group_element import (
|
|
28
|
+
LatticeEuclideanGroupElement)
|
|
29
|
+
from sage.geometry.polyhedron.ppl_lattice_polytope import (
|
|
30
|
+
LatticePolytope_PPL, LatticePolytope_PPL_class)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
########################################################################
|
|
34
|
+
class LatticePolygon_PPL_class(LatticePolytope_PPL_class):
|
|
35
|
+
"""
|
|
36
|
+
A lattice polygon.
|
|
37
|
+
|
|
38
|
+
This includes 2-dimensional polytopes as well as degenerate (0 and
|
|
39
|
+
1-dimensional) lattice polygons. Any polytope in 2d is a polygon.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
@cached_method
|
|
43
|
+
def ordered_vertices(self):
|
|
44
|
+
"""
|
|
45
|
+
Return the vertices of a lattice polygon in cyclic order.
|
|
46
|
+
|
|
47
|
+
OUTPUT:
|
|
48
|
+
|
|
49
|
+
A tuple of vertices ordered along the perimeter of the
|
|
50
|
+
polygon. The first point is arbitrary.
|
|
51
|
+
|
|
52
|
+
EXAMPLES::
|
|
53
|
+
|
|
54
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL
|
|
55
|
+
sage: square = LatticePolytope_PPL((0,0), (1,1), (0,1), (1,0))
|
|
56
|
+
sage: square.vertices()
|
|
57
|
+
((0, 0), (0, 1), (1, 0), (1, 1))
|
|
58
|
+
sage: square.ordered_vertices()
|
|
59
|
+
((0, 0), (1, 0), (1, 1), (0, 1))
|
|
60
|
+
"""
|
|
61
|
+
neighbors = dict()
|
|
62
|
+
if self.affine_dimension() < 2:
|
|
63
|
+
return self.vertices()
|
|
64
|
+
for c in self.minimized_constraints():
|
|
65
|
+
v1, v2 = self.vertices_saturating(c)
|
|
66
|
+
neighbors[v1] = [v2] + neighbors.get(v1, [])
|
|
67
|
+
neighbors[v2] = [v1] + neighbors.get(v2, [])
|
|
68
|
+
v_prev = self.vertices()[0]
|
|
69
|
+
v_curr = neighbors[v_prev][0]
|
|
70
|
+
result = [v_prev, v_curr]
|
|
71
|
+
while len(result) < self.n_vertices():
|
|
72
|
+
v1, v2 = neighbors[v_curr]
|
|
73
|
+
if v1 == v_prev:
|
|
74
|
+
v_next = v2
|
|
75
|
+
else:
|
|
76
|
+
v_next = v1
|
|
77
|
+
result.append(v_next)
|
|
78
|
+
v_prev = v_curr
|
|
79
|
+
v_curr = v_next
|
|
80
|
+
return tuple(result)
|
|
81
|
+
|
|
82
|
+
def _find_isomorphism_degenerate(self, polytope):
|
|
83
|
+
"""
|
|
84
|
+
Helper to pick an isomorphism of degenerate polygons.
|
|
85
|
+
|
|
86
|
+
INPUT:
|
|
87
|
+
|
|
88
|
+
- ``polytope`` -- a :class:`LatticePolytope_PPL_class`; the
|
|
89
|
+
polytope to compare with
|
|
90
|
+
|
|
91
|
+
EXAMPLES::
|
|
92
|
+
|
|
93
|
+
sage: # needs sage.libs.pari
|
|
94
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL, C_Polyhedron
|
|
95
|
+
sage: L1 = LatticePolytope_PPL(C_Polyhedron(2, 'empty'))
|
|
96
|
+
sage: L2 = LatticePolytope_PPL(C_Polyhedron(3, 'empty'))
|
|
97
|
+
sage: iso = L1.find_isomorphism(L2) # indirect doctest
|
|
98
|
+
sage: iso(L1) == L2
|
|
99
|
+
True
|
|
100
|
+
sage: iso = L1._find_isomorphism_degenerate(L2)
|
|
101
|
+
sage: iso(L1) == L2
|
|
102
|
+
True
|
|
103
|
+
sage: L1 = LatticePolytope_PPL((-1,4))
|
|
104
|
+
sage: L2 = LatticePolytope_PPL((2,1,5))
|
|
105
|
+
sage: iso = L1.find_isomorphism(L2)
|
|
106
|
+
sage: iso(L1) == L2
|
|
107
|
+
True
|
|
108
|
+
sage: L1 = LatticePolytope_PPL((-1,), (3,))
|
|
109
|
+
sage: L2 = LatticePolytope_PPL((2,1,5), (2,-3,5))
|
|
110
|
+
sage: iso = L1.find_isomorphism(L2)
|
|
111
|
+
sage: iso(L1) == L2
|
|
112
|
+
True
|
|
113
|
+
sage: L1 = LatticePolytope_PPL((-1,-1), (3,-1))
|
|
114
|
+
sage: L2 = LatticePolytope_PPL((2,1,5), (2,-3,5))
|
|
115
|
+
sage: iso = L1.find_isomorphism(L2)
|
|
116
|
+
sage: iso(L1) == L2
|
|
117
|
+
True
|
|
118
|
+
sage: L1 = LatticePolytope_PPL((-1,2), (3,1))
|
|
119
|
+
sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,4))
|
|
120
|
+
sage: iso = L1.find_isomorphism(L2)
|
|
121
|
+
sage: iso(L1) == L2
|
|
122
|
+
True
|
|
123
|
+
sage: L1 = LatticePolytope_PPL((-1,2), (3,2))
|
|
124
|
+
sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,4))
|
|
125
|
+
sage: L1.find_isomorphism(L2)
|
|
126
|
+
Traceback (most recent call last):
|
|
127
|
+
...
|
|
128
|
+
LatticePolytopesNotIsomorphicError: different number of integral points
|
|
129
|
+
sage: L1 = LatticePolytope_PPL((-1,2), (3,1))
|
|
130
|
+
sage: L2 = LatticePolytope_PPL((1,2,3),(1,2,5))
|
|
131
|
+
sage: L1.find_isomorphism(L2)
|
|
132
|
+
Traceback (most recent call last):
|
|
133
|
+
...
|
|
134
|
+
LatticePolytopesNotIsomorphicError: different number of integral points
|
|
135
|
+
"""
|
|
136
|
+
from sage.geometry.polyhedron.lattice_euclidean_group_element import \
|
|
137
|
+
LatticePolytopesNotIsomorphicError
|
|
138
|
+
polytope_vertices = polytope.vertices()
|
|
139
|
+
self_vertices = self.ordered_vertices()
|
|
140
|
+
# handle degenerate cases
|
|
141
|
+
if self.n_vertices() == 0:
|
|
142
|
+
A = zero_matrix(ZZ, polytope.space_dimension(), self.space_dimension())
|
|
143
|
+
b = zero_vector(ZZ, polytope.space_dimension())
|
|
144
|
+
return LatticeEuclideanGroupElement(A, b)
|
|
145
|
+
if self.n_vertices() == 1:
|
|
146
|
+
A = zero_matrix(ZZ, polytope.space_dimension(), self.space_dimension())
|
|
147
|
+
b = polytope_vertices[0]
|
|
148
|
+
return LatticeEuclideanGroupElement(A, b)
|
|
149
|
+
if self.n_vertices() == 2:
|
|
150
|
+
self_origin = self_vertices[0]
|
|
151
|
+
self_ray = self_vertices[1] - self_origin
|
|
152
|
+
polytope_origin = polytope_vertices[0]
|
|
153
|
+
polytope_ray = polytope_vertices[1] - polytope_origin
|
|
154
|
+
Ds, Us, Vs = self_ray.column().smith_form()
|
|
155
|
+
Dp, Up, Vp = polytope_ray.column().smith_form()
|
|
156
|
+
assert Vs.nrows() == Vs.ncols() == Vp.nrows() == Vp.ncols() == 1
|
|
157
|
+
assert abs(Vs[0, 0]) == abs(Vp[0, 0]) == 1
|
|
158
|
+
A = zero_matrix(ZZ, Dp.nrows(), Ds.nrows())
|
|
159
|
+
A[0, 0] = 1
|
|
160
|
+
A = Up.inverse() * A * Us * (Vs[0, 0] * Vp[0, 0])
|
|
161
|
+
b = polytope_origin - A*self_origin
|
|
162
|
+
try:
|
|
163
|
+
A = matrix(ZZ, A)
|
|
164
|
+
b = vector(ZZ, b)
|
|
165
|
+
except TypeError:
|
|
166
|
+
raise LatticePolytopesNotIsomorphicError('different lattice')
|
|
167
|
+
hom = LatticeEuclideanGroupElement(A, b)
|
|
168
|
+
if hom(self) == polytope:
|
|
169
|
+
return hom
|
|
170
|
+
raise LatticePolytopesNotIsomorphicError('different polygons')
|
|
171
|
+
|
|
172
|
+
def _find_cyclic_isomorphism_matching_edge(self, polytope,
|
|
173
|
+
polytope_origin, p_ray_left,
|
|
174
|
+
p_ray_right):
|
|
175
|
+
r"""
|
|
176
|
+
Helper to find an isomorphism of polygons.
|
|
177
|
+
|
|
178
|
+
INPUT:
|
|
179
|
+
|
|
180
|
+
- ``polytope`` -- the lattice polytope to compare to
|
|
181
|
+
|
|
182
|
+
- ``polytope_origin`` -- `\ZZ`-vector; a vertex of ``polytope``
|
|
183
|
+
|
|
184
|
+
- ``p_ray_left`` -- vector; the vector from ``polytope_origin``
|
|
185
|
+
to one of its neighboring vertices
|
|
186
|
+
|
|
187
|
+
- ``p_ray_right`` -- vector; the vector from
|
|
188
|
+
``polytope_origin`` to the other neighboring vertices
|
|
189
|
+
|
|
190
|
+
OUTPUT:
|
|
191
|
+
|
|
192
|
+
The element of the lattice Euclidean group that maps ``self``
|
|
193
|
+
to ``polytope`` with given origin and left/right neighboring
|
|
194
|
+
vertex. A
|
|
195
|
+
:class:`~sage.geometry.polyhedron.lattice_euclidean_group_element.LatticePolytopesNotIsomorphicError`
|
|
196
|
+
is raised if no such isomorphism exists.
|
|
197
|
+
|
|
198
|
+
EXAMPLES::
|
|
199
|
+
|
|
200
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL
|
|
201
|
+
sage: L1 = LatticePolytope_PPL((1,0),(0,1),(0,0))
|
|
202
|
+
sage: L2 = LatticePolytope_PPL((1,0,3),(0,1,0),(0,0,1))
|
|
203
|
+
sage: v0, v1, v2 = L2.vertices()
|
|
204
|
+
sage: L1._find_cyclic_isomorphism_matching_edge(L2, v0, v1 - v0, v2 - v0) # needs sage.libs.pari
|
|
205
|
+
The map A*x+b with A=
|
|
206
|
+
[ 0 1]
|
|
207
|
+
[-1 -1]
|
|
208
|
+
[ 1 3]
|
|
209
|
+
b =
|
|
210
|
+
(0, 1, 0)
|
|
211
|
+
"""
|
|
212
|
+
from sage.geometry.polyhedron.lattice_euclidean_group_element import \
|
|
213
|
+
LatticePolytopesNotIsomorphicError
|
|
214
|
+
polytope_matrix = block_matrix(1, 2, [p_ray_left.column(),
|
|
215
|
+
p_ray_right.column()])
|
|
216
|
+
self_vertices = self.ordered_vertices()
|
|
217
|
+
for i in range(len(self_vertices)):
|
|
218
|
+
# three consecutive vertices
|
|
219
|
+
v_left = self_vertices[(i+0) % len(self_vertices)]
|
|
220
|
+
v_origin = self_vertices[(i+1) % len(self_vertices)]
|
|
221
|
+
v_right = self_vertices[(i+2) % len(self_vertices)]
|
|
222
|
+
r_left = v_left-v_origin
|
|
223
|
+
r_right = v_right-v_origin
|
|
224
|
+
self_matrix = block_matrix(1, 2, [r_left.column(),
|
|
225
|
+
r_right.column()])
|
|
226
|
+
A = self_matrix.solve_left(polytope_matrix)
|
|
227
|
+
b = polytope_origin - A*v_origin
|
|
228
|
+
try:
|
|
229
|
+
A = matrix(ZZ, A)
|
|
230
|
+
b = vector(ZZ, b)
|
|
231
|
+
except TypeError:
|
|
232
|
+
continue
|
|
233
|
+
if A.elementary_divisors()[0:2] != [1, 1]:
|
|
234
|
+
continue
|
|
235
|
+
hom = LatticeEuclideanGroupElement(A, b)
|
|
236
|
+
if hom(self) == polytope:
|
|
237
|
+
return hom
|
|
238
|
+
raise LatticePolytopesNotIsomorphicError('different polygons')
|
|
239
|
+
|
|
240
|
+
def find_isomorphism(self, polytope):
|
|
241
|
+
r"""
|
|
242
|
+
Return a lattice isomorphism with ``polytope``.
|
|
243
|
+
|
|
244
|
+
INPUT:
|
|
245
|
+
|
|
246
|
+
- ``polytope`` -- a polytope, potentially higher-dimensional
|
|
247
|
+
|
|
248
|
+
OUTPUT:
|
|
249
|
+
|
|
250
|
+
A
|
|
251
|
+
:class:`~sage.geometry.polyhedron.lattice_euclidean_group_element.LatticeEuclideanGroupElement`. It
|
|
252
|
+
is not necessarily invertible if the affine dimension of
|
|
253
|
+
``self`` or ``polytope`` is not two. A
|
|
254
|
+
:class:`~sage.geometry.polyhedron.lattice_euclidean_group_element.LatticePolytopesNotIsomorphicError`
|
|
255
|
+
is raised if no such isomorphism exists.
|
|
256
|
+
|
|
257
|
+
EXAMPLES::
|
|
258
|
+
|
|
259
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL
|
|
260
|
+
sage: L1 = LatticePolytope_PPL((1,0),(0,1),(0,0))
|
|
261
|
+
sage: L2 = LatticePolytope_PPL((1,0,3),(0,1,0),(0,0,1))
|
|
262
|
+
sage: iso = L1.find_isomorphism(L2) # needs sage.libs.pari
|
|
263
|
+
sage: iso(L1) == L2 # needs sage.libs.pari
|
|
264
|
+
True
|
|
265
|
+
|
|
266
|
+
sage: L1 = LatticePolytope_PPL((0, 1), (3, 0), (0, 3), (1, 0))
|
|
267
|
+
sage: L2 = LatticePolytope_PPL((0,0,2,1),(0,1,2,0),(2,0,0,3),(2,3,0,0))
|
|
268
|
+
sage: iso = L1.find_isomorphism(L2) # needs sage.libs.pari
|
|
269
|
+
sage: iso(L1) == L2 # needs sage.libs.pari
|
|
270
|
+
True
|
|
271
|
+
|
|
272
|
+
The following polygons are isomorphic over `\QQ`, but not as
|
|
273
|
+
lattice polytopes::
|
|
274
|
+
|
|
275
|
+
sage: L1 = LatticePolytope_PPL((1,0),(0,1),(-1,-1))
|
|
276
|
+
sage: L2 = LatticePolytope_PPL((0, 0), (0, 1), (1, 0))
|
|
277
|
+
sage: L1.find_isomorphism(L2) # needs sage.libs.pari
|
|
278
|
+
Traceback (most recent call last):
|
|
279
|
+
...
|
|
280
|
+
LatticePolytopesNotIsomorphicError: different number of integral points
|
|
281
|
+
sage: L2.find_isomorphism(L1) # needs sage.libs.pari
|
|
282
|
+
Traceback (most recent call last):
|
|
283
|
+
...
|
|
284
|
+
LatticePolytopesNotIsomorphicError: different number of integral points
|
|
285
|
+
"""
|
|
286
|
+
from sage.geometry.polyhedron.lattice_euclidean_group_element import \
|
|
287
|
+
LatticePolytopesNotIsomorphicError
|
|
288
|
+
if polytope.affine_dimension() != self.affine_dimension():
|
|
289
|
+
raise LatticePolytopesNotIsomorphicError('different dimension')
|
|
290
|
+
polytope_vertices = polytope.vertices()
|
|
291
|
+
if len(polytope_vertices) != self.n_vertices():
|
|
292
|
+
raise LatticePolytopesNotIsomorphicError('different number of vertices')
|
|
293
|
+
self_vertices = self.ordered_vertices()
|
|
294
|
+
if len(polytope.integral_points()) != len(self.integral_points()):
|
|
295
|
+
raise LatticePolytopesNotIsomorphicError('different number of integral points')
|
|
296
|
+
|
|
297
|
+
if len(self_vertices) < 3:
|
|
298
|
+
return self._find_isomorphism_degenerate(polytope)
|
|
299
|
+
|
|
300
|
+
polytope_origin = polytope_vertices[0]
|
|
301
|
+
origin_P = C_Polyhedron(next(iter(polytope.minimized_generators())))
|
|
302
|
+
|
|
303
|
+
neighbors = []
|
|
304
|
+
for c in polytope.minimized_constraints():
|
|
305
|
+
if not c.is_inequality():
|
|
306
|
+
continue
|
|
307
|
+
if origin_P.relation_with(c).implies(Poly_Con_Relation.saturates()):
|
|
308
|
+
for i, g in enumerate(polytope.minimized_generators()):
|
|
309
|
+
if i == 0:
|
|
310
|
+
continue
|
|
311
|
+
g = C_Polyhedron(g)
|
|
312
|
+
if g.relation_with(c).implies(Poly_Con_Relation.saturates()):
|
|
313
|
+
neighbors.append(polytope_vertices[i])
|
|
314
|
+
break
|
|
315
|
+
|
|
316
|
+
p_ray_left = neighbors[0] - polytope_origin
|
|
317
|
+
p_ray_right = neighbors[1] - polytope_origin
|
|
318
|
+
try:
|
|
319
|
+
return self._find_cyclic_isomorphism_matching_edge(polytope, polytope_origin,
|
|
320
|
+
p_ray_left, p_ray_right)
|
|
321
|
+
except LatticePolytopesNotIsomorphicError:
|
|
322
|
+
pass
|
|
323
|
+
try:
|
|
324
|
+
return self._find_cyclic_isomorphism_matching_edge(polytope, polytope_origin,
|
|
325
|
+
p_ray_right, p_ray_left)
|
|
326
|
+
except LatticePolytopesNotIsomorphicError:
|
|
327
|
+
pass
|
|
328
|
+
raise LatticePolytopesNotIsomorphicError('different polygons')
|
|
329
|
+
|
|
330
|
+
def is_isomorphic(self, polytope):
|
|
331
|
+
"""
|
|
332
|
+
Test if ``self`` and ``polytope`` are isomorphic.
|
|
333
|
+
|
|
334
|
+
INPUT:
|
|
335
|
+
|
|
336
|
+
- ``polytope`` -- a lattice polytope
|
|
337
|
+
|
|
338
|
+
OUTPUT: boolean
|
|
339
|
+
|
|
340
|
+
EXAMPLES::
|
|
341
|
+
|
|
342
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL
|
|
343
|
+
sage: L1 = LatticePolytope_PPL((1,0),(0,1),(0,0))
|
|
344
|
+
sage: L2 = LatticePolytope_PPL((1,0,3),(0,1,0),(0,0,1))
|
|
345
|
+
sage: L1.is_isomorphic(L2) # needs sage.libs.pari
|
|
346
|
+
True
|
|
347
|
+
"""
|
|
348
|
+
from sage.geometry.polyhedron.lattice_euclidean_group_element import \
|
|
349
|
+
LatticePolytopesNotIsomorphicError
|
|
350
|
+
try:
|
|
351
|
+
self.find_isomorphism(polytope)
|
|
352
|
+
return True
|
|
353
|
+
except LatticePolytopesNotIsomorphicError:
|
|
354
|
+
return False
|
|
355
|
+
|
|
356
|
+
def sub_polytopes(self):
|
|
357
|
+
"""
|
|
358
|
+
Return a list of all lattice sub-polygons up to isomorphism.
|
|
359
|
+
|
|
360
|
+
OUTPUT:
|
|
361
|
+
|
|
362
|
+
All non-empty sub-lattice polytopes up to isomorphism. This
|
|
363
|
+
includes ``self`` as improper sub-polytope, but excludes the
|
|
364
|
+
empty polytope. Isomorphic sub-polytopes that can be embedded
|
|
365
|
+
in different places are only returned once.
|
|
366
|
+
|
|
367
|
+
EXAMPLES::
|
|
368
|
+
|
|
369
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL
|
|
370
|
+
sage: P1xP1 = LatticePolytope_PPL((1,0), (0,1), (-1,0), (0,-1))
|
|
371
|
+
sage: P1xP1.sub_polytopes() # needs sage.libs.pari
|
|
372
|
+
(A 2-dimensional lattice polytope in ZZ^2 with 4 vertices,
|
|
373
|
+
A 2-dimensional lattice polytope in ZZ^2 with 3 vertices,
|
|
374
|
+
A 2-dimensional lattice polytope in ZZ^2 with 3 vertices,
|
|
375
|
+
A 1-dimensional lattice polytope in ZZ^2 with 2 vertices,
|
|
376
|
+
A 1-dimensional lattice polytope in ZZ^2 with 2 vertices,
|
|
377
|
+
A 0-dimensional lattice polytope in ZZ^2 with 1 vertex)
|
|
378
|
+
"""
|
|
379
|
+
subpolytopes = [self]
|
|
380
|
+
todo = list(subpolytopes)
|
|
381
|
+
while todo:
|
|
382
|
+
polytope = todo.pop()
|
|
383
|
+
for p in polytope.sub_polytope_generator():
|
|
384
|
+
if p.is_empty():
|
|
385
|
+
continue
|
|
386
|
+
if any(p.is_isomorphic(q) for q in subpolytopes):
|
|
387
|
+
continue
|
|
388
|
+
subpolytopes.append(p)
|
|
389
|
+
todo.append(p)
|
|
390
|
+
return tuple(subpolytopes)
|
|
391
|
+
|
|
392
|
+
def plot(self):
|
|
393
|
+
"""
|
|
394
|
+
Plot the lattice polygon.
|
|
395
|
+
|
|
396
|
+
OUTPUT: a graphics object
|
|
397
|
+
|
|
398
|
+
EXAMPLES::
|
|
399
|
+
|
|
400
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polytope import LatticePolytope_PPL
|
|
401
|
+
sage: P = LatticePolytope_PPL((1,0), (0,1), (0,0), (2,2))
|
|
402
|
+
sage: P.plot() # needs sage.plot
|
|
403
|
+
Graphics object consisting of 6 graphics primitives
|
|
404
|
+
sage: LatticePolytope_PPL([0], [1]).plot() # needs sage.plot
|
|
405
|
+
Graphics object consisting of 3 graphics primitives
|
|
406
|
+
sage: LatticePolytope_PPL([0]).plot() # needs sage.plot
|
|
407
|
+
Graphics object consisting of 2 graphics primitives
|
|
408
|
+
"""
|
|
409
|
+
from sage.plot.point import point2d
|
|
410
|
+
from sage.plot.polygon import polygon2d
|
|
411
|
+
vertices = self.ordered_vertices()
|
|
412
|
+
points = self.integral_points()
|
|
413
|
+
if self.space_dimension() == 1:
|
|
414
|
+
vertices = [vector(ZZ, (v[0], 0)) for v in vertices]
|
|
415
|
+
points = [vector(ZZ, (p[0], 0)) for p in points]
|
|
416
|
+
point_plot = sum(point2d(p, pointsize=100, color='red')
|
|
417
|
+
for p in points)
|
|
418
|
+
polygon_plot = polygon2d(vertices, alpha=0.2, color='green',
|
|
419
|
+
zorder=-1, thickness=2)
|
|
420
|
+
return polygon_plot + point_plot
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
########################################################################
|
|
424
|
+
#
|
|
425
|
+
# Reflexive lattice polygons and their subpolygons
|
|
426
|
+
#
|
|
427
|
+
########################################################################
|
|
428
|
+
|
|
429
|
+
@cached_function
|
|
430
|
+
def polar_P2_polytope():
|
|
431
|
+
"""
|
|
432
|
+
The polar of the `P^2` polytope.
|
|
433
|
+
|
|
434
|
+
EXAMPLES::
|
|
435
|
+
|
|
436
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polygon import polar_P2_polytope
|
|
437
|
+
sage: polar_P2_polytope()
|
|
438
|
+
A 2-dimensional lattice polytope in ZZ^2 with 3 vertices
|
|
439
|
+
sage: _.vertices()
|
|
440
|
+
((0, 0), (0, 3), (3, 0))
|
|
441
|
+
"""
|
|
442
|
+
return LatticePolytope_PPL((0, 0), (3, 0), (0, 3))
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@cached_function
|
|
446
|
+
def polar_P1xP1_polytope():
|
|
447
|
+
r"""
|
|
448
|
+
The polar of the `P^1 \times P^1` polytope.
|
|
449
|
+
|
|
450
|
+
EXAMPLES::
|
|
451
|
+
|
|
452
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polygon import polar_P1xP1_polytope
|
|
453
|
+
sage: polar_P1xP1_polytope()
|
|
454
|
+
A 2-dimensional lattice polytope in ZZ^2 with 4 vertices
|
|
455
|
+
sage: _.vertices()
|
|
456
|
+
((0, 0), (0, 2), (2, 0), (2, 2))
|
|
457
|
+
"""
|
|
458
|
+
return LatticePolytope_PPL((0, 0), (2, 0), (0, 2), (2, 2))
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@cached_function
|
|
462
|
+
def polar_P2_112_polytope():
|
|
463
|
+
"""
|
|
464
|
+
The polar of the `P^2[1,1,2]` polytope.
|
|
465
|
+
|
|
466
|
+
EXAMPLES::
|
|
467
|
+
|
|
468
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polygon import polar_P2_112_polytope
|
|
469
|
+
sage: polar_P2_112_polytope()
|
|
470
|
+
A 2-dimensional lattice polytope in ZZ^2 with 3 vertices
|
|
471
|
+
sage: _.vertices()
|
|
472
|
+
((0, 0), (0, 2), (4, 0))
|
|
473
|
+
"""
|
|
474
|
+
return LatticePolytope_PPL((0, 0), (4, 0), (0, 2))
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
@cached_function
|
|
478
|
+
def subpolygons_of_polar_P2():
|
|
479
|
+
"""
|
|
480
|
+
The lattice sub-polygons of the polar `P^2` polytope.
|
|
481
|
+
|
|
482
|
+
OUTPUT: a tuple of lattice polytopes
|
|
483
|
+
|
|
484
|
+
EXAMPLES::
|
|
485
|
+
|
|
486
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polygon import subpolygons_of_polar_P2
|
|
487
|
+
sage: len(subpolygons_of_polar_P2()) # needs sage.libs.pari
|
|
488
|
+
27
|
|
489
|
+
"""
|
|
490
|
+
return polar_P2_polytope().sub_polytopes()
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@cached_function
|
|
494
|
+
def subpolygons_of_polar_P2_112():
|
|
495
|
+
"""
|
|
496
|
+
The lattice sub-polygons of the polar `P^2[1,1,2]` polytope.
|
|
497
|
+
|
|
498
|
+
OUTPUT: a tuple of lattice polytopes
|
|
499
|
+
|
|
500
|
+
EXAMPLES::
|
|
501
|
+
|
|
502
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polygon import subpolygons_of_polar_P2_112
|
|
503
|
+
sage: len(subpolygons_of_polar_P2_112()) # needs sage.libs.pari
|
|
504
|
+
28
|
|
505
|
+
"""
|
|
506
|
+
return polar_P2_112_polytope().sub_polytopes()
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
@cached_function
|
|
510
|
+
def subpolygons_of_polar_P1xP1():
|
|
511
|
+
r"""
|
|
512
|
+
The lattice sub-polygons of the polar `P^1 \times P^1` polytope.
|
|
513
|
+
|
|
514
|
+
OUTPUT: a tuple of lattice polytopes
|
|
515
|
+
|
|
516
|
+
EXAMPLES::
|
|
517
|
+
|
|
518
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polygon import subpolygons_of_polar_P1xP1
|
|
519
|
+
sage: len(subpolygons_of_polar_P1xP1()) # needs sage.libs.pari
|
|
520
|
+
20
|
|
521
|
+
"""
|
|
522
|
+
return polar_P1xP1_polytope().sub_polytopes()
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@cached_function
|
|
526
|
+
def sub_reflexive_polygons():
|
|
527
|
+
"""
|
|
528
|
+
Return all lattice sub-polygons of reflexive polygons.
|
|
529
|
+
|
|
530
|
+
OUTPUT:
|
|
531
|
+
|
|
532
|
+
A tuple of all lattice sub-polygons. Each sub-polygon is returned
|
|
533
|
+
as a pair sub-polygon, containing reflexive polygon.
|
|
534
|
+
|
|
535
|
+
EXAMPLES::
|
|
536
|
+
|
|
537
|
+
sage: # needs sage.libs.pari
|
|
538
|
+
sage: from sage.geometry.polyhedron.ppl_lattice_polygon import sub_reflexive_polygons
|
|
539
|
+
sage: l = sub_reflexive_polygons(); l[5]
|
|
540
|
+
(A 2-dimensional lattice polytope in ZZ^2 with 6 vertices,
|
|
541
|
+
A 2-dimensional lattice polytope in ZZ^2 with 3 vertices)
|
|
542
|
+
sage: len(l)
|
|
543
|
+
33
|
|
544
|
+
"""
|
|
545
|
+
result = []
|
|
546
|
+
|
|
547
|
+
def add_result(subpolygon, ambient):
|
|
548
|
+
if not any(subpolygon.is_isomorphic(p[0]) for p in result):
|
|
549
|
+
result.append((subpolygon, ambient))
|
|
550
|
+
for p in subpolygons_of_polar_P2():
|
|
551
|
+
add_result(p, polar_P2_polytope())
|
|
552
|
+
for p in subpolygons_of_polar_P2_112():
|
|
553
|
+
add_result(p, polar_P2_112_polytope())
|
|
554
|
+
for p in subpolygons_of_polar_P1xP1():
|
|
555
|
+
add_result(p, polar_P1xP1_polytope())
|
|
556
|
+
return tuple(result)
|