passagemath-linbox 10.6.32__cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of passagemath-linbox might be problematic. Click here for more details.
- passagemath_linbox-10.6.32.dist-info/METADATA +100 -0
- passagemath_linbox-10.6.32.dist-info/RECORD +73 -0
- passagemath_linbox-10.6.32.dist-info/WHEEL +6 -0
- passagemath_linbox-10.6.32.dist-info/top_level.txt +2 -0
- passagemath_linbox.libs/libfflas-d452d784.so.1.0.0 +0 -0
- passagemath_linbox.libs/libffpack-32579c9b.so.1.0.0 +0 -0
- passagemath_linbox.libs/libflint-66e12231.so.21.0.0 +0 -0
- passagemath_linbox.libs/libgd-76eb082b.so.3.0.11 +0 -0
- passagemath_linbox.libs/libgfortran-83c28eba.so.5.0.0 +0 -0
- passagemath_linbox.libs/libgivaro-fc554fc9.so.9.2.1 +0 -0
- passagemath_linbox.libs/libgmp-6e109695.so.10.5.0 +0 -0
- passagemath_linbox.libs/libgmpxx-ecb9d6e3.so.4.7.0 +0 -0
- passagemath_linbox.libs/libiml-aeb1d147.so.0.1.1 +0 -0
- passagemath_linbox.libs/liblinbox-f1d24fc1.so.0.0.0 +0 -0
- passagemath_linbox.libs/libm4ri-9da2b874.so.1.0.0 +0 -0
- passagemath_linbox.libs/libm4rie-cf8cc058.so.1.0.0 +0 -0
- passagemath_linbox.libs/libmpfr-82690d50.so.6.2.1 +0 -0
- passagemath_linbox.libs/libopenblasp-r0-6dcb67f9.3.29.so +0 -0
- passagemath_linbox.libs/libpng16-b4a91cd1.so.16.43.0 +0 -0
- passagemath_linbox.libs/libquadmath-2284e583.so.0.0.0 +0 -0
- sage/all__sagemath_linbox.py +2 -0
- sage/geometry/all__sagemath_linbox.py +1 -0
- sage/geometry/integral_points.pxi +1426 -0
- sage/geometry/integral_points_integer_dense.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/geometry/integral_points_integer_dense.pyx +7 -0
- sage/libs/all__sagemath_linbox.py +1 -0
- sage/libs/iml.pxd +10 -0
- sage/libs/linbox/__init__.py +1 -0
- sage/libs/linbox/conversion.pxd +185 -0
- sage/libs/linbox/fflas.pxd +189 -0
- sage/libs/linbox/givaro.pxd +109 -0
- sage/libs/linbox/linbox.pxd +219 -0
- sage/libs/linbox/linbox_flint_interface.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/libs/linbox/linbox_flint_interface.pxd +18 -0
- sage/libs/linbox/linbox_flint_interface.pyx +192 -0
- sage/libs/m4ri.pxd +198 -0
- sage/libs/m4rie.pxd +204 -0
- sage/matrix/all__sagemath_linbox.py +1 -0
- sage/matrix/matrix_cyclo_linbox.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_cyclo_linbox.pyx +361 -0
- sage/matrix/matrix_gf2e_dense.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_gf2e_dense.pxd +15 -0
- sage/matrix/matrix_gf2e_dense.pyx +1573 -0
- sage/matrix/matrix_integer_iml.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_integer_iml.pyx +316 -0
- sage/matrix/matrix_integer_linbox.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_integer_linbox.pxd +5 -0
- sage/matrix/matrix_integer_linbox.pyx +358 -0
- sage/matrix/matrix_integer_sparse_linbox.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_integer_sparse_linbox.pyx +465 -0
- sage/matrix/matrix_mod2_dense.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_mod2_dense.pxd +14 -0
- sage/matrix/matrix_mod2_dense.pyx +2789 -0
- sage/matrix/matrix_modn_dense_double.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_modn_dense_double.pyx +179 -0
- sage/matrix/matrix_modn_dense_float.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_modn_dense_float.pyx +154 -0
- sage/matrix/matrix_modn_sparse.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_modn_sparse.pyx +871 -0
- sage/matrix/matrix_rational_linbox.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/matrix_rational_linbox.pyx +36 -0
- sage/matrix/misc.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/matrix/misc.pyx +418 -0
- sage/modules/all__sagemath_linbox.py +1 -0
- sage/modules/numpy_util.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/modules/numpy_util.pxd +10 -0
- sage/modules/numpy_util.pyx +136 -0
- sage/modules/vector_mod2_dense.cpython-313-x86_64-linux-gnu.so +0 -0
- sage/modules/vector_mod2_dense.pxd +11 -0
- sage/modules/vector_mod2_dense.pyx +547 -0
- sage/rings/all__sagemath_linbox.py +1 -0
- sage/rings/finite_rings/all__sagemath_linbox.py +1 -0
- sage/rings/polynomial/all__sagemath_linbox.py +1 -0
|
@@ -0,0 +1,1426 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-linbox
|
|
2
|
+
# sage.doctest: needs sage.geometry.polyhedron
|
|
3
|
+
r"""
|
|
4
|
+
Cython helper methods to compute integral points in polyhedra.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# ***************************************************************************
|
|
8
|
+
# Copyright (C) 2010 Volker Braun <vbraun.name@gmail.com>
|
|
9
|
+
#
|
|
10
|
+
# This program is free software: you can redistribute it and/or modify
|
|
11
|
+
# it under the terms of the GNU General Public License as published by
|
|
12
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
13
|
+
# (at your option) any later version.
|
|
14
|
+
# https://www.gnu.org/licenses/
|
|
15
|
+
# ***************************************************************************
|
|
16
|
+
|
|
17
|
+
from cysignals.signals cimport sig_check
|
|
18
|
+
import copy
|
|
19
|
+
import itertools
|
|
20
|
+
|
|
21
|
+
from sage.matrix.constructor import matrix, column_matrix, vector, diagonal_matrix
|
|
22
|
+
from sage.rings.integer_ring import ZZ
|
|
23
|
+
from sage.rings.integer cimport Integer
|
|
24
|
+
from sage.arith.misc import gcd
|
|
25
|
+
from sage.arith.functions import lcm
|
|
26
|
+
from sage.misc.misc_c import prod
|
|
27
|
+
from sage.modules.free_module import FreeModule
|
|
28
|
+
|
|
29
|
+
##############################################################################
|
|
30
|
+
# The basic idea to enumerate the lattice points in the parallelotope
|
|
31
|
+
# is to use the Smith normal form of the ray coordinate matrix to
|
|
32
|
+
# bring the lattice into a "nice" basis. Here is the straightforward
|
|
33
|
+
# implementation. Note that you do not need to reduce to the
|
|
34
|
+
# full-dimensional case, the Smith normal form takes care of that for
|
|
35
|
+
# you.
|
|
36
|
+
#
|
|
37
|
+
## def parallelotope_points(spanning_points, lattice):
|
|
38
|
+
## # compute points in the open parallelotope, see [BK2001]
|
|
39
|
+
## R = matrix(spanning_points).transpose()
|
|
40
|
+
## D,U,V = R.smith_form()
|
|
41
|
+
## e = D.diagonal() # the elementary divisors
|
|
42
|
+
## d = prod(e) # the determinant
|
|
43
|
+
## u = U.inverse().columns() # generators for gp(semigroup)
|
|
44
|
+
##
|
|
45
|
+
## # "inverse" of the ray matrix as far as possible over ZZ
|
|
46
|
+
## # R*Rinv == diagonal_matrix([d]*D.ncols() + [0]*(D.nrows()-D.ncols()))
|
|
47
|
+
## # If R is full rank, this is Rinv = matrix(ZZ, R.inverse() * d)
|
|
48
|
+
## Dinv = D.transpose()
|
|
49
|
+
## for i in range(D.ncols()):
|
|
50
|
+
## Dinv[i,i] = d/D[i,i]
|
|
51
|
+
## Rinv = V * Dinv * U
|
|
52
|
+
##
|
|
53
|
+
## gens = []
|
|
54
|
+
## for b in CartesianProduct(*[ range(i) for i in e ]):
|
|
55
|
+
## # this is our generator modulo the lattice spanned by the rays
|
|
56
|
+
## gen_mod_rays = sum( b_i*u_i for b_i, u_i in zip(b,u) )
|
|
57
|
+
## q_times_d = Rinv * gen_mod_rays
|
|
58
|
+
## q_times_d = vector(ZZ,[ q_i % d for q_i in q_times_d ])
|
|
59
|
+
## gen = lattice(R*q_times_d / d)
|
|
60
|
+
## gen.set_immutable()
|
|
61
|
+
## gens.append(gen)
|
|
62
|
+
## assert(len(gens) == d)
|
|
63
|
+
## return tuple(gens)
|
|
64
|
+
#
|
|
65
|
+
# The problem with the naive implementation is that it is slow:
|
|
66
|
+
#
|
|
67
|
+
# 1. You can simplify some of the matrix multiplications
|
|
68
|
+
#
|
|
69
|
+
# 2. The inner loop keeps creating new matrices and vectors, which
|
|
70
|
+
# is slow. It needs to recycle objects. Instead of creating a new
|
|
71
|
+
# lattice point again and again, change the entries of an
|
|
72
|
+
# existing lattice point and then copy it!
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
cpdef tuple parallelotope_points(spanning_points, lattice):
|
|
76
|
+
r"""
|
|
77
|
+
Return integral points in the parallelotope starting at the origin
|
|
78
|
+
and spanned by the ``spanning_points``.
|
|
79
|
+
|
|
80
|
+
See :meth:`~ConvexRationalPolyhedralCone.semigroup_generators` for a description of the
|
|
81
|
+
algorithm.
|
|
82
|
+
|
|
83
|
+
INPUT:
|
|
84
|
+
|
|
85
|
+
- ``spanning_points`` -- a non-empty list of linearly independent
|
|
86
|
+
rays (`\ZZ`-vectors or :class:`toric lattice
|
|
87
|
+
<sage.geometry.toric_lattice.ToricLatticeFactory>` elements),
|
|
88
|
+
not necessarily primitive lattice points.
|
|
89
|
+
|
|
90
|
+
OUTPUT:
|
|
91
|
+
|
|
92
|
+
The tuple of all lattice points in the half-open parallelotope
|
|
93
|
+
spanned by the rays `r_i`,
|
|
94
|
+
|
|
95
|
+
.. MATH::
|
|
96
|
+
|
|
97
|
+
\mathop{par}(\{r_i\}) =
|
|
98
|
+
\sum_{0\leq a_i < 1} a_i r_i
|
|
99
|
+
|
|
100
|
+
By half-open parallelotope, we mean that the
|
|
101
|
+
points in the facets not meeting the origin are omitted.
|
|
102
|
+
|
|
103
|
+
EXAMPLES:
|
|
104
|
+
|
|
105
|
+
Note how the points on the outward-facing factes are omitted::
|
|
106
|
+
|
|
107
|
+
sage: from sage.geometry.integral_points import parallelotope_points
|
|
108
|
+
sage: rays = list(map(vector, [(2,0), (0,2)]))
|
|
109
|
+
sage: parallelotope_points(rays, ZZ^2)
|
|
110
|
+
((0, 0), (0, 1), (1, 0), (1, 1))
|
|
111
|
+
|
|
112
|
+
The rays can also be toric lattice points::
|
|
113
|
+
|
|
114
|
+
sage: rays = list(map(ToricLattice(2), [(2,0), (0,2)]))
|
|
115
|
+
sage: parallelotope_points(rays, ToricLattice(2))
|
|
116
|
+
(N(0, 0), N(0, 1), N(1, 0), N(1, 1))
|
|
117
|
+
|
|
118
|
+
A non-smooth cone::
|
|
119
|
+
|
|
120
|
+
sage: c = Cone([ (1,0), (1,2) ])
|
|
121
|
+
sage: parallelotope_points(c.rays(), c.lattice())
|
|
122
|
+
(N(0, 0), N(1, 1))
|
|
123
|
+
|
|
124
|
+
A :exc:`ValueError` is raised if the ``spanning_points`` are not
|
|
125
|
+
linearly independent::
|
|
126
|
+
|
|
127
|
+
sage: rays = list(map(ToricLattice(2), [(1,1)]*2))
|
|
128
|
+
sage: parallelotope_points(rays, ToricLattice(2))
|
|
129
|
+
Traceback (most recent call last):
|
|
130
|
+
...
|
|
131
|
+
ValueError: The spanning points are not linearly independent!
|
|
132
|
+
|
|
133
|
+
TESTS::
|
|
134
|
+
|
|
135
|
+
sage: rays = list(map(vector,[(-3, -2, -3, -2), (-2, -1, -8, 5), (1, 9, -7, -4), (-3, -1, -2, 2)]))
|
|
136
|
+
sage: len(parallelotope_points(rays, ZZ^4))
|
|
137
|
+
967
|
|
138
|
+
"""
|
|
139
|
+
cdef MatrixClass VDinv
|
|
140
|
+
cdef MatrixClass R = matrix(spanning_points).transpose()
|
|
141
|
+
e, d, VDinv = ray_matrix_normal_form(R)
|
|
142
|
+
cdef tuple points = loop_over_parallelotope_points(e, d, VDinv, R, lattice)
|
|
143
|
+
for p in points:
|
|
144
|
+
p.set_immutable()
|
|
145
|
+
return points
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
cpdef tuple ray_matrix_normal_form(R):
|
|
149
|
+
r"""
|
|
150
|
+
Compute the Smith normal form of the ray matrix for
|
|
151
|
+
:func:`parallelotope_points`.
|
|
152
|
+
|
|
153
|
+
INPUT:
|
|
154
|
+
|
|
155
|
+
- ``R`` -- `\ZZ`-matrix whose columns are the rays spanning the
|
|
156
|
+
parallelotope
|
|
157
|
+
|
|
158
|
+
OUTPUT: a tuple containing ``e``, ``d``, and ``VDinv``
|
|
159
|
+
|
|
160
|
+
EXAMPLES::
|
|
161
|
+
|
|
162
|
+
sage: from sage.geometry.integral_points import ray_matrix_normal_form
|
|
163
|
+
sage: R = column_matrix(ZZ,[3,3,3])
|
|
164
|
+
sage: ray_matrix_normal_form(R)
|
|
165
|
+
([3], 3, [1])
|
|
166
|
+
"""
|
|
167
|
+
D,U,V = R.smith_form()
|
|
168
|
+
e = D.diagonal() # the elementary divisors
|
|
169
|
+
cdef Integer d = prod(e) # the determinant
|
|
170
|
+
if d == ZZ.zero():
|
|
171
|
+
raise ValueError('The spanning points are not linearly independent!')
|
|
172
|
+
cdef int i
|
|
173
|
+
Dinv = diagonal_matrix(ZZ, [ d // e[i] for i in range(D.ncols()) ])
|
|
174
|
+
VDinv = V * Dinv
|
|
175
|
+
return (e, d, VDinv)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# The optimized version avoids constructing new matrices, vectors, and lattice points
|
|
179
|
+
cpdef tuple loop_over_parallelotope_points(e, d, MatrixClass VDinv,
|
|
180
|
+
MatrixClass R, lattice,
|
|
181
|
+
A=None, b=None):
|
|
182
|
+
r"""
|
|
183
|
+
The inner loop of :func:`parallelotope_points`.
|
|
184
|
+
|
|
185
|
+
INPUT:
|
|
186
|
+
|
|
187
|
+
See :meth:`parallelotope_points` for ``e``, ``d``, ``VDinv``, ``R``, ``lattice``.
|
|
188
|
+
|
|
189
|
+
- ``A``, ``b`` -- either both ``None`` or a vector and number. If
|
|
190
|
+
present, only the parallelotope points satisfying `A x \leq b`
|
|
191
|
+
are returned.
|
|
192
|
+
|
|
193
|
+
OUTPUT:
|
|
194
|
+
|
|
195
|
+
The points of the half-open parallelotope as a tuple of lattice
|
|
196
|
+
points.
|
|
197
|
+
|
|
198
|
+
EXAMPLES::
|
|
199
|
+
|
|
200
|
+
sage: e = [3]
|
|
201
|
+
sage: d = prod(e)
|
|
202
|
+
sage: VDinv = matrix(ZZ, [[1]])
|
|
203
|
+
sage: R = column_matrix(ZZ,[3,3,3])
|
|
204
|
+
sage: lattice = ZZ^3
|
|
205
|
+
sage: from sage.geometry.integral_points import loop_over_parallelotope_points
|
|
206
|
+
sage: loop_over_parallelotope_points(e, d, VDinv, R, lattice)
|
|
207
|
+
((0, 0, 0), (1, 1, 1), (2, 2, 2))
|
|
208
|
+
|
|
209
|
+
sage: A = vector(ZZ, [1,0,0])
|
|
210
|
+
sage: b = 1
|
|
211
|
+
sage: loop_over_parallelotope_points(e, d, VDinv, R, lattice, A, b)
|
|
212
|
+
((0, 0, 0), (1, 1, 1))
|
|
213
|
+
"""
|
|
214
|
+
cdef int i, j
|
|
215
|
+
cdef int dim = VDinv.nrows()
|
|
216
|
+
cdef int ambient_dim = R.nrows()
|
|
217
|
+
s = ZZ.zero() # summation variable
|
|
218
|
+
cdef list gens = []
|
|
219
|
+
gen = lattice(ZZ.zero())
|
|
220
|
+
cdef VectorClass q_times_d = vector(ZZ, dim)
|
|
221
|
+
for base in itertools.product(*[ range(i) for i in e ]):
|
|
222
|
+
for i in range(dim):
|
|
223
|
+
s = ZZ.zero()
|
|
224
|
+
for j in range(dim):
|
|
225
|
+
s += VDinv.get_unsafe(i,j) * base[j]
|
|
226
|
+
q_times_d.set_unsafe(i, s % d)
|
|
227
|
+
for i in range(ambient_dim):
|
|
228
|
+
s = ZZ.zero()
|
|
229
|
+
for j in range(dim):
|
|
230
|
+
s += R.get_unsafe(i,j) * q_times_d.get_unsafe(j)
|
|
231
|
+
gen[i] = s / d
|
|
232
|
+
if A is not None:
|
|
233
|
+
s = ZZ.zero()
|
|
234
|
+
for i in range(ambient_dim):
|
|
235
|
+
s += A[i] * gen[i]
|
|
236
|
+
if s > b:
|
|
237
|
+
continue
|
|
238
|
+
gens.append(copy.copy(gen))
|
|
239
|
+
if A is None:
|
|
240
|
+
assert(len(gens) == d)
|
|
241
|
+
return tuple(gens)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
##############################################################################
|
|
245
|
+
cpdef tuple simplex_points(vertices):
|
|
246
|
+
r"""
|
|
247
|
+
Return the integral points in a lattice simplex.
|
|
248
|
+
|
|
249
|
+
INPUT:
|
|
250
|
+
|
|
251
|
+
- ``vertices`` -- an iterable of integer coordinate vectors. The
|
|
252
|
+
indices of vertices that span the simplex under
|
|
253
|
+
consideration.
|
|
254
|
+
|
|
255
|
+
OUTPUT:
|
|
256
|
+
|
|
257
|
+
A tuple containing the integral point coordinates as `\ZZ`-vectors.
|
|
258
|
+
|
|
259
|
+
EXAMPLES::
|
|
260
|
+
|
|
261
|
+
sage: from sage.geometry.integral_points import simplex_points
|
|
262
|
+
sage: simplex_points([(1,2,3), (2,3,7), (-2,-3,-11)])
|
|
263
|
+
((-2, -3, -11), (0, 0, -2), (1, 2, 3), (2, 3, 7))
|
|
264
|
+
|
|
265
|
+
The simplex need not be full-dimensional::
|
|
266
|
+
|
|
267
|
+
sage: simplex = Polyhedron([(1,2,3,5), (2,3,7,5), (-2,-3,-11,5)])
|
|
268
|
+
sage: simplex_points(simplex.Vrepresentation())
|
|
269
|
+
((2, 3, 7, 5), (0, 0, -2, 5), (-2, -3, -11, 5), (1, 2, 3, 5))
|
|
270
|
+
|
|
271
|
+
sage: simplex_points([(2,3,7)])
|
|
272
|
+
((2, 3, 7),)
|
|
273
|
+
|
|
274
|
+
TESTS::
|
|
275
|
+
|
|
276
|
+
sage: v = [(1,0,7,-1), (-2,-2,4,-3), (-1,-1,-1,4), (2,9,0,-5), (-2,-1,5,1)]
|
|
277
|
+
sage: simplex = Polyhedron(v); simplex
|
|
278
|
+
A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices
|
|
279
|
+
sage: pts = simplex_points(simplex.Vrepresentation())
|
|
280
|
+
sage: len(pts)
|
|
281
|
+
49
|
|
282
|
+
sage: for p in pts: p.set_immutable()
|
|
283
|
+
sage: len(set(pts))
|
|
284
|
+
49
|
|
285
|
+
|
|
286
|
+
sage: all(simplex.contains(p) for p in pts)
|
|
287
|
+
True
|
|
288
|
+
|
|
289
|
+
sage: v = [(4,-1,-1,-1), (-1,4,-1,-1), (-1,-1,4,-1), (-1,-1,-1,4), (-1,-1,-1,-1)]
|
|
290
|
+
sage: P4mirror = Polyhedron(v); P4mirror
|
|
291
|
+
A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices
|
|
292
|
+
sage: len(simplex_points(P4mirror.Vrepresentation()))
|
|
293
|
+
126
|
|
294
|
+
|
|
295
|
+
sage: vertices = list(map(vector, [(1,2,3), (2,3,7), (-2,-3,-11)]))
|
|
296
|
+
sage: for v in vertices: v.set_immutable()
|
|
297
|
+
sage: simplex_points(vertices)
|
|
298
|
+
((-2, -3, -11), (0, 0, -2), (1, 2, 3), (2, 3, 7))
|
|
299
|
+
"""
|
|
300
|
+
cdef list rays = [vector(ZZ, list(v)) for v in vertices]
|
|
301
|
+
if not rays:
|
|
302
|
+
return ()
|
|
303
|
+
cdef VectorClass origin = rays.pop()
|
|
304
|
+
origin.set_immutable()
|
|
305
|
+
if not rays:
|
|
306
|
+
return (origin,)
|
|
307
|
+
translate_points(rays, origin)
|
|
308
|
+
|
|
309
|
+
# Find equation Ax<=b that cuts out simplex from parallelotope
|
|
310
|
+
cdef MatrixClass Rt = matrix(ZZ, rays)
|
|
311
|
+
cdef MatrixClass R = Rt.transpose()
|
|
312
|
+
cdef int nrays = len(rays)
|
|
313
|
+
cdef Integer b
|
|
314
|
+
M = FreeModule(ZZ, nrays)
|
|
315
|
+
if R.is_square():
|
|
316
|
+
b = abs(R.det())
|
|
317
|
+
A = R.solve_left(M([b]*nrays))
|
|
318
|
+
else:
|
|
319
|
+
RtR = Rt * R
|
|
320
|
+
b = abs(RtR.det())
|
|
321
|
+
A = RtR.solve_left(M([b]*nrays)) * Rt
|
|
322
|
+
|
|
323
|
+
e, d, VDinv = ray_matrix_normal_form(R)
|
|
324
|
+
lattice = origin.parent()
|
|
325
|
+
points = loop_over_parallelotope_points(e, d, VDinv, R, lattice, A, b) + tuple(rays)
|
|
326
|
+
translate_points(points, -origin)
|
|
327
|
+
return points
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
cdef translate_points(v_list, VectorClass delta):
|
|
331
|
+
r"""
|
|
332
|
+
Add ``delta`` to each vector in ``v_list``.
|
|
333
|
+
"""
|
|
334
|
+
cdef int dim = delta.degree()
|
|
335
|
+
cdef int i
|
|
336
|
+
for v in v_list:
|
|
337
|
+
for i in range(dim):
|
|
338
|
+
v[i] -= delta.get_unsafe(i)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
##############################################################################
|
|
342
|
+
# For points with "small" coordinates (that is, fitting into a small
|
|
343
|
+
# rectangular bounding box) it is faster to naively enumerate the
|
|
344
|
+
# points. This saves the overhead of triangulating the polytope etc.
|
|
345
|
+
|
|
346
|
+
cpdef rectangular_box_points(list box_min, list box_max,
|
|
347
|
+
polyhedron=None, count_only=False,
|
|
348
|
+
return_saturated=False):
|
|
349
|
+
r"""
|
|
350
|
+
Return the integral points in the lattice bounding box that are
|
|
351
|
+
also contained in the given polyhedron.
|
|
352
|
+
|
|
353
|
+
INPUT:
|
|
354
|
+
|
|
355
|
+
- ``box_min`` -- list of integers; the minimal value for each
|
|
356
|
+
coordinate of the rectangular bounding box
|
|
357
|
+
|
|
358
|
+
- ``box_max`` -- list of integers; the maximal value for each
|
|
359
|
+
coordinate of the rectangular bounding box
|
|
360
|
+
|
|
361
|
+
- ``polyhedron`` -- a
|
|
362
|
+
:class:`~sage.geometry.polyhedron.base.Polyhedron_base`, a PPL
|
|
363
|
+
:class:`~ppl.polyhedron.C_Polyhedron`, or ``None`` (default)
|
|
364
|
+
|
|
365
|
+
- ``count_only`` -- boolean (default: ``False``); whether to
|
|
366
|
+
return only the total number of vertices, and not their
|
|
367
|
+
coordinates. Enabling this option speeds up the
|
|
368
|
+
enumeration. Cannot be combined with the ``return_saturated``
|
|
369
|
+
option.
|
|
370
|
+
|
|
371
|
+
- ``return_saturated`` -- boolean (default: ``False``); whether to
|
|
372
|
+
also return which inequalities are saturated for each point of
|
|
373
|
+
the polyhedron. Enabling this slows down the enumeration. Cannot
|
|
374
|
+
be combined with the ``count_only`` option.
|
|
375
|
+
|
|
376
|
+
OUTPUT:
|
|
377
|
+
|
|
378
|
+
By default, this function returns a tuple containing the integral
|
|
379
|
+
points of the rectangular box spanned by ``box_min`` and ``box_max``
|
|
380
|
+
and that lie inside the ``polyhedron``. For sufficiently large
|
|
381
|
+
bounding boxes, this are all integral points of the polyhedron.
|
|
382
|
+
|
|
383
|
+
If no polyhedron is specified, all integral points of the
|
|
384
|
+
rectangular box are returned.
|
|
385
|
+
|
|
386
|
+
If ``count_only`` is specified, only the total number (an integer)
|
|
387
|
+
of found lattice points is returned.
|
|
388
|
+
|
|
389
|
+
If ``return_saturated`` is enabled, then for each integral point a
|
|
390
|
+
pair ``(point, Hrep)`` is returned where ``point`` is the point
|
|
391
|
+
and ``Hrep`` is the set of indices of the H-representation objects
|
|
392
|
+
that are saturated at the point.
|
|
393
|
+
|
|
394
|
+
ALGORITHM:
|
|
395
|
+
|
|
396
|
+
This function implements the naive algorithm towards counting
|
|
397
|
+
integral points. Given min and max of vertex coordinates, it
|
|
398
|
+
iterates over all points in the bounding box and checks whether
|
|
399
|
+
they lie in the polyhedron. The following optimizations are
|
|
400
|
+
implemented:
|
|
401
|
+
|
|
402
|
+
* Cython: Use machine integers and optimizing C/C++ compiler
|
|
403
|
+
where possible, arbitrary precision integers where necessary.
|
|
404
|
+
Bounds checking, no compile time limits.
|
|
405
|
+
|
|
406
|
+
* Unwind inner loop (and next-to-inner loop):
|
|
407
|
+
|
|
408
|
+
.. MATH::
|
|
409
|
+
|
|
410
|
+
Ax \leq b
|
|
411
|
+
\quad \Leftrightarrow \quad
|
|
412
|
+
a_1 x_1 ~\leq~ b - \sum_{i=2}^d a_i x_i
|
|
413
|
+
|
|
414
|
+
so we only have to evaluate `a_1 * x_1` in the inner loop.
|
|
415
|
+
|
|
416
|
+
* Coordinates are permuted to make the longest box edge the
|
|
417
|
+
inner loop. The inner loop is optimized to run very fast, so
|
|
418
|
+
its best to do as much work as possible there.
|
|
419
|
+
|
|
420
|
+
* Continuously reorder inequalities and test the most
|
|
421
|
+
restrictive inequalities first.
|
|
422
|
+
|
|
423
|
+
* Use convexity and only find first and last allowed point in
|
|
424
|
+
the inner loop. The points in-between must be points of the
|
|
425
|
+
polyhedron, too.
|
|
426
|
+
|
|
427
|
+
EXAMPLES::
|
|
428
|
+
|
|
429
|
+
sage: from sage.geometry.integral_points import rectangular_box_points
|
|
430
|
+
sage: rectangular_box_points([0,0,0],[1,2,3])
|
|
431
|
+
((0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3),
|
|
432
|
+
(0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 1, 3),
|
|
433
|
+
(0, 2, 0), (0, 2, 1), (0, 2, 2), (0, 2, 3),
|
|
434
|
+
(1, 0, 0), (1, 0, 1), (1, 0, 2), (1, 0, 3),
|
|
435
|
+
(1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 1, 3),
|
|
436
|
+
(1, 2, 0), (1, 2, 1), (1, 2, 2), (1, 2, 3))
|
|
437
|
+
|
|
438
|
+
sage: from sage.geometry.integral_points import rectangular_box_points
|
|
439
|
+
sage: rectangular_box_points([0,0,0],[1,2,3], count_only=True)
|
|
440
|
+
24
|
|
441
|
+
|
|
442
|
+
sage: cell24 = polytopes.twenty_four_cell()
|
|
443
|
+
sage: rectangular_box_points([-1]*4, [1]*4, cell24)
|
|
444
|
+
((-1, 0, 0, 0), (0, -1, 0, 0), (0, 0, -1, 0), (0, 0, 0, -1),
|
|
445
|
+
(0, 0, 0, 0),
|
|
446
|
+
(0, 0, 0, 1), (0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0))
|
|
447
|
+
sage: d = 3
|
|
448
|
+
sage: dilated_cell24 = d*cell24
|
|
449
|
+
sage: len( rectangular_box_points([-d]*4, [d]*4, dilated_cell24) )
|
|
450
|
+
305
|
|
451
|
+
|
|
452
|
+
sage: d = 6
|
|
453
|
+
sage: dilated_cell24 = d*cell24
|
|
454
|
+
sage: len( rectangular_box_points([-d]*4, [d]*4, dilated_cell24) )
|
|
455
|
+
3625
|
|
456
|
+
|
|
457
|
+
sage: rectangular_box_points([-d]*4, [d]*4, dilated_cell24, count_only=True)
|
|
458
|
+
3625
|
|
459
|
+
|
|
460
|
+
sage: polytope = Polyhedron([(-4,-3,-2,-1),(3,1,1,1),(1,2,1,1),(1,1,3,0),(1,3,2,4)])
|
|
461
|
+
sage: pts = rectangular_box_points([-4]*4, [4]*4, polytope); pts
|
|
462
|
+
((-4, -3, -2, -1), (-1, 0, 0, 1), (0, 1, 1, 1), (1, 1, 1, 1), (1, 1, 3, 0),
|
|
463
|
+
(1, 2, 1, 1), (1, 2, 2, 2), (1, 3, 2, 4), (2, 1, 1, 1), (3, 1, 1, 1))
|
|
464
|
+
sage: all(polytope.contains(p) for p in pts)
|
|
465
|
+
True
|
|
466
|
+
|
|
467
|
+
sage: set(map(tuple,pts)) == \
|
|
468
|
+
....: set([(-4,-3,-2,-1),(3,1,1,1),(1,2,1,1),(1,1,3,0),(1,3,2,4),
|
|
469
|
+
....: (0,1,1,1),(1,2,2,2),(-1,0,0,1),(1,1,1,1),(2,1,1,1)]) # computed with PALP
|
|
470
|
+
True
|
|
471
|
+
|
|
472
|
+
Long ints and non-integral polyhedra are explicitly allowed::
|
|
473
|
+
|
|
474
|
+
sage: polytope = Polyhedron([[1], [10*pi.n()]], base_ring=RDF) # needs sage.symbolic
|
|
475
|
+
sage: len(rectangular_box_points([-100], [100], polytope)) # needs sage.symbolic
|
|
476
|
+
31
|
|
477
|
+
|
|
478
|
+
sage: halfplane = Polyhedron(ieqs=[(-1,1,0)])
|
|
479
|
+
sage: rectangular_box_points([0,-1+10^50], [0,1+10^50])
|
|
480
|
+
((0, 99999999999999999999999999999999999999999999999999),
|
|
481
|
+
(0, 100000000000000000000000000000000000000000000000000),
|
|
482
|
+
(0, 100000000000000000000000000000000000000000000000001))
|
|
483
|
+
sage: len( rectangular_box_points([0,-100+10^50], [1,100+10^50], halfplane) )
|
|
484
|
+
201
|
|
485
|
+
|
|
486
|
+
Using a PPL polyhedron::
|
|
487
|
+
|
|
488
|
+
sage: # needs pplpy
|
|
489
|
+
sage: from ppl import Variable, Generator_System, C_Polyhedron, point
|
|
490
|
+
sage: gs = Generator_System()
|
|
491
|
+
sage: x = Variable(0); y = Variable(1); z = Variable(2)
|
|
492
|
+
sage: gs.insert(point(0*x + 1*y + 0*z))
|
|
493
|
+
sage: gs.insert(point(0*x + 1*y + 3*z))
|
|
494
|
+
sage: gs.insert(point(3*x + 1*y + 0*z))
|
|
495
|
+
sage: gs.insert(point(3*x + 1*y + 3*z))
|
|
496
|
+
sage: poly = C_Polyhedron(gs)
|
|
497
|
+
sage: rectangular_box_points([0]*3, [3]*3, poly)
|
|
498
|
+
((0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 1, 3), (1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 1, 3),
|
|
499
|
+
(2, 1, 0), (2, 1, 1), (2, 1, 2), (2, 1, 3), (3, 1, 0), (3, 1, 1), (3, 1, 2), (3, 1, 3))
|
|
500
|
+
|
|
501
|
+
Optionally, return the information about the saturated inequalities as well::
|
|
502
|
+
|
|
503
|
+
sage: cube = polytopes.cube()
|
|
504
|
+
sage: cube.Hrepresentation(0)
|
|
505
|
+
An inequality (-1, 0, 0) x + 1 >= 0
|
|
506
|
+
sage: cube.Hrepresentation(1)
|
|
507
|
+
An inequality (0, -1, 0) x + 1 >= 0
|
|
508
|
+
sage: cube.Hrepresentation(2)
|
|
509
|
+
An inequality (0, 0, -1) x + 1 >= 0
|
|
510
|
+
sage: rectangular_box_points([0]*3, [1]*3, cube, return_saturated=True)
|
|
511
|
+
(((0, 0, 0), frozenset()),
|
|
512
|
+
((0, 0, 1), frozenset({2})),
|
|
513
|
+
((0, 1, 0), frozenset({1})),
|
|
514
|
+
((0, 1, 1), frozenset({1, 2})),
|
|
515
|
+
((1, 0, 0), frozenset({0})),
|
|
516
|
+
((1, 0, 1), frozenset({0, 2})),
|
|
517
|
+
((1, 1, 0), frozenset({0, 1})),
|
|
518
|
+
((1, 1, 1), frozenset({0, 1, 2})))
|
|
519
|
+
|
|
520
|
+
TESTS:
|
|
521
|
+
|
|
522
|
+
Check that this can be interrupted, see :issue:`20781`::
|
|
523
|
+
|
|
524
|
+
sage: ieqs = [(-1, -1, -1, -1, -1, -1, -1, -1, -1),
|
|
525
|
+
....: (0, -1, 0, 0, 0, 0, 0, 0, 0),
|
|
526
|
+
....: (0, -1, 0, 2, -1, 0, 0, 0, 0),
|
|
527
|
+
....: (0, 0, -1, -1, 2, -1, 0, 0, 0),
|
|
528
|
+
....: (0, 2, 0, -1, 0, 0, 0, 0, 0),
|
|
529
|
+
....: (0, 0, 0, 0, 0, 0, 0, -1, 2),
|
|
530
|
+
....: (1, 0, 2, 0, -1, 0, 0, 0, 0),
|
|
531
|
+
....: (0, 0, 0, 0, -1, 2, -1, 0, 0),
|
|
532
|
+
....: (0, 0, 0, 0, 0, 0, 0, 0, -1),
|
|
533
|
+
....: (0, 0, 0, 0, 0, -1, 2, -1, 0),
|
|
534
|
+
....: (0, 0, 0, 0, 0, 0, -1, 2, -1)]
|
|
535
|
+
sage: P = Polyhedron(ieqs=ieqs)
|
|
536
|
+
sage: from sage.doctest.util import ensure_interruptible_after
|
|
537
|
+
sage: with ensure_interruptible_after(0.5): P.integral_points()
|
|
538
|
+
"""
|
|
539
|
+
assert len(box_min) == len(box_max)
|
|
540
|
+
assert not (count_only and return_saturated)
|
|
541
|
+
cdef int d = len(box_min)
|
|
542
|
+
cdef int i, j
|
|
543
|
+
cdef list diameter = sorted([(box_max[i]-box_min[i], i) for i in range(d)],
|
|
544
|
+
reverse=True)
|
|
545
|
+
cdef list diameter_value = [x[0] for x in diameter]
|
|
546
|
+
cdef list diameter_index = [x[1] for x in diameter]
|
|
547
|
+
|
|
548
|
+
# Construct the inverse permutation
|
|
549
|
+
cdef list orig_perm = list(range(len(diameter_index)))
|
|
550
|
+
for i, j in enumerate(diameter_index):
|
|
551
|
+
orig_perm[j] = i
|
|
552
|
+
|
|
553
|
+
box_min = perm_action(diameter_index, box_min)
|
|
554
|
+
box_max = perm_action(diameter_index, box_max)
|
|
555
|
+
cdef InequalityCollection inequalities = InequalityCollection(polyhedron, diameter_index, box_min, box_max)
|
|
556
|
+
|
|
557
|
+
if count_only:
|
|
558
|
+
return loop_over_rectangular_box_points(box_min, box_max, inequalities, d, count_only)
|
|
559
|
+
|
|
560
|
+
cdef list points = []
|
|
561
|
+
cdef VectorClass v = vector(ZZ, d)
|
|
562
|
+
if not return_saturated:
|
|
563
|
+
for p in loop_over_rectangular_box_points(box_min, box_max, inequalities, d, count_only):
|
|
564
|
+
# v = vector(ZZ, perm_action(orig_perm, p)) # too slow
|
|
565
|
+
for i in range(d):
|
|
566
|
+
v.set_unsafe(i, Integer(p[orig_perm[i]]))
|
|
567
|
+
v_copy = copy.copy(v)
|
|
568
|
+
v_copy.set_immutable()
|
|
569
|
+
points.append(v_copy)
|
|
570
|
+
else:
|
|
571
|
+
for p, saturated in loop_over_rectangular_box_points_saturated(box_min, box_max,
|
|
572
|
+
inequalities, d):
|
|
573
|
+
for i in range(d):
|
|
574
|
+
v.set_unsafe(i, Integer(p[orig_perm[i]]))
|
|
575
|
+
v_copy = copy.copy(v)
|
|
576
|
+
v_copy.set_immutable()
|
|
577
|
+
points.append( (v_copy, saturated) )
|
|
578
|
+
|
|
579
|
+
return tuple(points)
|
|
580
|
+
|
|
581
|
+
cdef list perm_action(list p, list lst):
|
|
582
|
+
"""
|
|
583
|
+
Return the action of a permutation ``p`` of `(0, ..., n-1)`
|
|
584
|
+
on a list of length `n`.
|
|
585
|
+
"""
|
|
586
|
+
return [lst[i] for i in p]
|
|
587
|
+
|
|
588
|
+
cdef loop_over_rectangular_box_points(list box_min, list box_max,
|
|
589
|
+
InequalityCollection inequalities,
|
|
590
|
+
int d, bint count_only):
|
|
591
|
+
"""
|
|
592
|
+
The inner loop of :func:`rectangular_box_points`.
|
|
593
|
+
|
|
594
|
+
INPUT:
|
|
595
|
+
|
|
596
|
+
- ``box_min``, ``box_max`` -- the bounding box
|
|
597
|
+
|
|
598
|
+
- ``inequalities`` -- a :class:`InequalityCollection` containing
|
|
599
|
+
the inequalities defining the polyhedron
|
|
600
|
+
|
|
601
|
+
- ``d`` -- the ambient space dimension
|
|
602
|
+
|
|
603
|
+
- ``count_only`` -- whether to only return the total number of
|
|
604
|
+
lattice points
|
|
605
|
+
|
|
606
|
+
OUTPUT: the integral points in the bounding box satisfying all
|
|
607
|
+
inequalities
|
|
608
|
+
"""
|
|
609
|
+
cdef int inc
|
|
610
|
+
cdef Integer i_min, i_max
|
|
611
|
+
if count_only:
|
|
612
|
+
points = 0
|
|
613
|
+
else:
|
|
614
|
+
points = []
|
|
615
|
+
cdef list p = list(box_min)
|
|
616
|
+
inequalities.prepare_next_to_inner_loop(p)
|
|
617
|
+
while True:
|
|
618
|
+
sig_check()
|
|
619
|
+
inequalities.prepare_inner_loop(p)
|
|
620
|
+
i_min = box_min[0]
|
|
621
|
+
i_max = box_max[0]
|
|
622
|
+
# Find the lower bound for the allowed region
|
|
623
|
+
while i_min <= i_max:
|
|
624
|
+
if inequalities.are_satisfied(i_min):
|
|
625
|
+
break
|
|
626
|
+
i_min += 1
|
|
627
|
+
# Find the upper bound for the allowed region
|
|
628
|
+
while i_min <= i_max:
|
|
629
|
+
if inequalities.are_satisfied(i_max):
|
|
630
|
+
break
|
|
631
|
+
i_max -= 1
|
|
632
|
+
# The points i_min .. i_max are contained in the polyhedron
|
|
633
|
+
if count_only:
|
|
634
|
+
if i_max >= i_min:
|
|
635
|
+
points += i_max - i_min + 1
|
|
636
|
+
else:
|
|
637
|
+
i = i_min
|
|
638
|
+
while i <= i_max:
|
|
639
|
+
p[0] = i
|
|
640
|
+
points.append(tuple(p))
|
|
641
|
+
i += 1
|
|
642
|
+
# finally increment the other entries in p to move on to next inner loop
|
|
643
|
+
inc = 1
|
|
644
|
+
if d == 1:
|
|
645
|
+
return points
|
|
646
|
+
while True:
|
|
647
|
+
if p[inc] == box_max[inc]:
|
|
648
|
+
p[inc] = box_min[inc]
|
|
649
|
+
inc += 1
|
|
650
|
+
if inc == d:
|
|
651
|
+
return points
|
|
652
|
+
else:
|
|
653
|
+
p[inc] += 1
|
|
654
|
+
break
|
|
655
|
+
if inc > 1:
|
|
656
|
+
inequalities.prepare_next_to_inner_loop(p)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
cdef loop_over_rectangular_box_points_saturated(list box_min, list box_max,
|
|
660
|
+
InequalityCollection inequalities,
|
|
661
|
+
int d):
|
|
662
|
+
"""
|
|
663
|
+
The analog of :func:`rectangular_box_points` except that it keeps
|
|
664
|
+
track of which inequalities are saturated.
|
|
665
|
+
|
|
666
|
+
INPUT:
|
|
667
|
+
|
|
668
|
+
See :func:`rectangular_box_points`.
|
|
669
|
+
|
|
670
|
+
OUTPUT:
|
|
671
|
+
|
|
672
|
+
The integral points in the bounding box satisfying all
|
|
673
|
+
inequalities, each point being returned by a coordinate vector and
|
|
674
|
+
a set of H-representation object indices.
|
|
675
|
+
"""
|
|
676
|
+
cdef int inc
|
|
677
|
+
cdef list points = []
|
|
678
|
+
cdef list p = list(box_min)
|
|
679
|
+
inequalities.prepare_next_to_inner_loop(p)
|
|
680
|
+
while True:
|
|
681
|
+
inequalities.prepare_inner_loop(p)
|
|
682
|
+
i_min = box_min[0]
|
|
683
|
+
i_max = box_max[0]
|
|
684
|
+
# Find the lower bound for the allowed region
|
|
685
|
+
while i_min <= i_max:
|
|
686
|
+
if inequalities.are_satisfied(i_min):
|
|
687
|
+
break
|
|
688
|
+
i_min += 1
|
|
689
|
+
# Find the upper bound for the allowed region
|
|
690
|
+
while i_min <= i_max:
|
|
691
|
+
if inequalities.are_satisfied(i_max):
|
|
692
|
+
break
|
|
693
|
+
i_max -= 1
|
|
694
|
+
# The points i_min .. i_max are contained in the polyhedron
|
|
695
|
+
i = i_min
|
|
696
|
+
while i <= i_max:
|
|
697
|
+
p[0] = i
|
|
698
|
+
saturated = inequalities.satisfied_as_equalities(i)
|
|
699
|
+
points.append( (tuple(p), saturated) )
|
|
700
|
+
i += 1
|
|
701
|
+
# finally increment the other entries in p to move on to next inner loop
|
|
702
|
+
inc = 1
|
|
703
|
+
if d == 1:
|
|
704
|
+
return points
|
|
705
|
+
while True:
|
|
706
|
+
if p[inc] == box_max[inc]:
|
|
707
|
+
p[inc] = box_min[inc]
|
|
708
|
+
inc += 1
|
|
709
|
+
if inc == d:
|
|
710
|
+
return points
|
|
711
|
+
else:
|
|
712
|
+
p[inc] += 1
|
|
713
|
+
break
|
|
714
|
+
if inc > 1:
|
|
715
|
+
inequalities.prepare_next_to_inner_loop(p)
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
cdef class Inequality_generic:
|
|
719
|
+
r"""
|
|
720
|
+
An inequality whose coefficients are arbitrary Python/Sage objects
|
|
721
|
+
|
|
722
|
+
INPUT:
|
|
723
|
+
|
|
724
|
+
- ``A`` -- list of coefficients
|
|
725
|
+
|
|
726
|
+
- ``b`` -- element
|
|
727
|
+
|
|
728
|
+
OUTPUT: inequality `A x + b \geq 0`
|
|
729
|
+
|
|
730
|
+
EXAMPLES::
|
|
731
|
+
|
|
732
|
+
sage: from sage.geometry.integral_points import Inequality_generic
|
|
733
|
+
sage: Inequality_generic([2 * pi, sqrt(3), 7/2], -5.5) # needs sage.symbolic
|
|
734
|
+
generic: (2*pi, sqrt(3), 7/2) x + -5.50000000000000 >= 0
|
|
735
|
+
"""
|
|
736
|
+
|
|
737
|
+
cdef list A
|
|
738
|
+
cdef object b
|
|
739
|
+
cdef object coeff
|
|
740
|
+
cdef object cache
|
|
741
|
+
# The index of the inequality in the polyhedron H-representation
|
|
742
|
+
cdef int index
|
|
743
|
+
|
|
744
|
+
def __cinit__(self, list A, b, int index=-1):
|
|
745
|
+
"""
|
|
746
|
+
The Cython constructor.
|
|
747
|
+
|
|
748
|
+
INPUT:
|
|
749
|
+
|
|
750
|
+
See :class:`Inequality_generic`.
|
|
751
|
+
|
|
752
|
+
EXAMPLES::
|
|
753
|
+
|
|
754
|
+
sage: from sage.geometry.integral_points import Inequality_generic
|
|
755
|
+
sage: Inequality_generic([2 * pi, sqrt(3), 7/2], -5.5) # needs sage.symbolic
|
|
756
|
+
generic: (2*pi, sqrt(3), 7/2) x + -5.50000000000000 >= 0
|
|
757
|
+
"""
|
|
758
|
+
self.A = A
|
|
759
|
+
self.b = b
|
|
760
|
+
self.coeff = 0
|
|
761
|
+
self.cache = 0
|
|
762
|
+
self.index = int(index)
|
|
763
|
+
|
|
764
|
+
def __repr__(self):
|
|
765
|
+
"""
|
|
766
|
+
Return a string representation.
|
|
767
|
+
|
|
768
|
+
OUTPUT: string
|
|
769
|
+
|
|
770
|
+
EXAMPLES::
|
|
771
|
+
|
|
772
|
+
sage: from sage.geometry.integral_points import Inequality_generic
|
|
773
|
+
sage: Inequality_generic([2,3,7], -5).__repr__()
|
|
774
|
+
'generic: (2, 3, 7) x + -5 >= 0'
|
|
775
|
+
"""
|
|
776
|
+
s = 'generic: ('
|
|
777
|
+
s += ', '.join(str(self.A[i]) for i in range(len(self.A)))
|
|
778
|
+
s += ') x + ' + str(self.b) + ' >= 0'
|
|
779
|
+
return s
|
|
780
|
+
|
|
781
|
+
cdef prepare_next_to_inner_loop(self, p):
|
|
782
|
+
"""
|
|
783
|
+
In :class:`Inequality_int` this method is used to peel of the
|
|
784
|
+
next-to-inner loop.
|
|
785
|
+
|
|
786
|
+
See :meth:`InequalityCollection.prepare_inner_loop` for more details.
|
|
787
|
+
"""
|
|
788
|
+
pass
|
|
789
|
+
|
|
790
|
+
cdef prepare_inner_loop(self, p):
|
|
791
|
+
"""
|
|
792
|
+
Peel off the inner loop.
|
|
793
|
+
|
|
794
|
+
See :meth:`InequalityCollection.prepare_inner_loop` for more details.
|
|
795
|
+
"""
|
|
796
|
+
cdef int j
|
|
797
|
+
self.coeff = self.A[0]
|
|
798
|
+
self.cache = self.b
|
|
799
|
+
for j in range(1, len(self.A)):
|
|
800
|
+
self.cache += self.A[j] * p[j]
|
|
801
|
+
|
|
802
|
+
cdef bint is_not_satisfied(self, inner_loop_variable) except -1:
|
|
803
|
+
r"""
|
|
804
|
+
Test the inequality, using the cached value from :meth:`prepare_inner_loop`
|
|
805
|
+
|
|
806
|
+
OUTPUT: boolean; whether the inequality is not satisfied
|
|
807
|
+
"""
|
|
808
|
+
return inner_loop_variable * self.coeff + self.cache < 0
|
|
809
|
+
|
|
810
|
+
cdef bint is_equality(Inequality_generic self, int inner_loop_variable) except -1:
|
|
811
|
+
r"""
|
|
812
|
+
Test the inequality, using the cached value from :meth:`prepare_inner_loop`
|
|
813
|
+
|
|
814
|
+
OUTPUT:
|
|
815
|
+
|
|
816
|
+
boolean. Given the inequality `Ax + b \geq 0`, this method
|
|
817
|
+
returns whether the equality `Ax + b = 0` is satisfied.
|
|
818
|
+
"""
|
|
819
|
+
return inner_loop_variable * self.coeff + self.cache == 0
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
# if dim>20 then we always use the generic inequalities (Inequality_generic)
|
|
823
|
+
DEF INEQ_INT_MAX_DIM = 20
|
|
824
|
+
|
|
825
|
+
cdef class Inequality_int:
|
|
826
|
+
r"""
|
|
827
|
+
Fast version of inequality in the case that all coefficients fit
|
|
828
|
+
into machine ints.
|
|
829
|
+
|
|
830
|
+
INPUT:
|
|
831
|
+
|
|
832
|
+
- ``A`` -- list of integers
|
|
833
|
+
|
|
834
|
+
- ``b`` -- integer
|
|
835
|
+
|
|
836
|
+
- ``max_abs_coordinates`` -- the maximum of the coordinates that
|
|
837
|
+
one wants to evaluate the coordinates on; used for overflow
|
|
838
|
+
checking
|
|
839
|
+
|
|
840
|
+
OUTPUT:
|
|
841
|
+
|
|
842
|
+
Inequality `A x + b \geq 0`. A :exc:`OverflowError` is raised if a
|
|
843
|
+
machine integer is not long enough to hold the results. A
|
|
844
|
+
:exc:`ValueError` is raised if some of the input is not integral.
|
|
845
|
+
|
|
846
|
+
EXAMPLES::
|
|
847
|
+
|
|
848
|
+
sage: from sage.geometry.integral_points import Inequality_int
|
|
849
|
+
sage: Inequality_int([2,3,7], -5, [10]*3)
|
|
850
|
+
integer: (2, 3, 7) x + -5 >= 0
|
|
851
|
+
|
|
852
|
+
sage: Inequality_int([1]*21, -5, [10]*21)
|
|
853
|
+
Traceback (most recent call last):
|
|
854
|
+
...
|
|
855
|
+
OverflowError: Dimension limit exceeded.
|
|
856
|
+
|
|
857
|
+
sage: Inequality_int([2,3/2,7], -5, [10]*3)
|
|
858
|
+
Traceback (most recent call last):
|
|
859
|
+
...
|
|
860
|
+
ValueError: Not integral.
|
|
861
|
+
|
|
862
|
+
sage: Inequality_int([2,3,7], -5.2, [10]*3)
|
|
863
|
+
Traceback (most recent call last):
|
|
864
|
+
...
|
|
865
|
+
ValueError: Not integral.
|
|
866
|
+
|
|
867
|
+
sage: Inequality_int([2,3,7], -5*10^50, [10]*3) # actual error message can differ between 32 and 64 bit
|
|
868
|
+
Traceback (most recent call last):
|
|
869
|
+
...
|
|
870
|
+
OverflowError: ...
|
|
871
|
+
|
|
872
|
+
TESTS:
|
|
873
|
+
|
|
874
|
+
Check that :issue:`21993` is fixed::
|
|
875
|
+
|
|
876
|
+
sage: Inequality_int([18560500, -89466500], 108027, [178933, 37121])
|
|
877
|
+
Traceback (most recent call last):
|
|
878
|
+
...
|
|
879
|
+
OverflowError: ...
|
|
880
|
+
"""
|
|
881
|
+
cdef int A[INEQ_INT_MAX_DIM]
|
|
882
|
+
cdef int b
|
|
883
|
+
cdef int dim
|
|
884
|
+
# the innermost coefficient
|
|
885
|
+
cdef int coeff
|
|
886
|
+
cdef int cache
|
|
887
|
+
# the next-to-innermost coefficient
|
|
888
|
+
cdef int coeff_next
|
|
889
|
+
cdef int cache_next
|
|
890
|
+
# The index of the inequality in the polyhedron H-representation
|
|
891
|
+
cdef int index
|
|
892
|
+
|
|
893
|
+
cdef int _to_int(self, x) except? -999:
|
|
894
|
+
if x not in ZZ:
|
|
895
|
+
raise ValueError('Not integral.')
|
|
896
|
+
return int(x) # raises OverflowError in Cython if necessary
|
|
897
|
+
|
|
898
|
+
def __cinit__(self, list A, b, list max_abs_coordinates, int index=-1):
|
|
899
|
+
"""
|
|
900
|
+
The Cython constructor.
|
|
901
|
+
|
|
902
|
+
See :class:`Inequality_int` for input.
|
|
903
|
+
|
|
904
|
+
EXAMPLES::
|
|
905
|
+
|
|
906
|
+
sage: from sage.geometry.integral_points import Inequality_int
|
|
907
|
+
sage: Inequality_int([2,3,7], -5, [10]*3)
|
|
908
|
+
integer: (2, 3, 7) x + -5 >= 0
|
|
909
|
+
"""
|
|
910
|
+
cdef int i
|
|
911
|
+
self.dim = self._to_int(len(A))
|
|
912
|
+
self.index = int(index)
|
|
913
|
+
if self.dim < 1 or self.dim > INEQ_INT_MAX_DIM:
|
|
914
|
+
raise OverflowError('Dimension limit exceeded.')
|
|
915
|
+
for i in range(self.dim):
|
|
916
|
+
self.A[i] = self._to_int(A[i])
|
|
917
|
+
self.b = self._to_int(b)
|
|
918
|
+
self.coeff = self.A[0]
|
|
919
|
+
if self.dim > 0:
|
|
920
|
+
self.coeff_next = self.A[1]
|
|
921
|
+
# finally, make sure that there cannot be any overflow during the enumeration
|
|
922
|
+
self._to_int(abs(ZZ(b)) + sum( abs(ZZ(A[i])) * ZZ(max_abs_coordinates[i])
|
|
923
|
+
for i in range(self.dim) ))
|
|
924
|
+
|
|
925
|
+
def __repr__(self):
|
|
926
|
+
"""
|
|
927
|
+
Return a string representation.
|
|
928
|
+
|
|
929
|
+
OUTPUT: string
|
|
930
|
+
|
|
931
|
+
EXAMPLES::
|
|
932
|
+
|
|
933
|
+
sage: from sage.geometry.integral_points import Inequality_int
|
|
934
|
+
sage: Inequality_int([2,3,7], -5, [10]*3).__repr__()
|
|
935
|
+
'integer: (2, 3, 7) x + -5 >= 0'
|
|
936
|
+
"""
|
|
937
|
+
s = 'integer: ('
|
|
938
|
+
s += ', '.join(str(self.A[i]) for i in range(self.dim))
|
|
939
|
+
s += ') x + ' + str(self.b) + ' >= 0'
|
|
940
|
+
return s
|
|
941
|
+
|
|
942
|
+
cdef prepare_next_to_inner_loop(Inequality_int self, p):
|
|
943
|
+
"""
|
|
944
|
+
Peel off the next-to-inner loop.
|
|
945
|
+
|
|
946
|
+
See :meth:`InequalityCollection.prepare_inner_loop` for more details.
|
|
947
|
+
"""
|
|
948
|
+
cdef int j
|
|
949
|
+
self.cache_next = self.b
|
|
950
|
+
for j in range(2, self.dim):
|
|
951
|
+
self.cache_next += self.A[j] * p[j]
|
|
952
|
+
|
|
953
|
+
cdef prepare_inner_loop(Inequality_int self, p):
|
|
954
|
+
"""
|
|
955
|
+
Peel off the inner loop.
|
|
956
|
+
|
|
957
|
+
See :meth:`InequalityCollection.prepare_inner_loop` for more details.
|
|
958
|
+
"""
|
|
959
|
+
cdef int j
|
|
960
|
+
if self.dim > 1:
|
|
961
|
+
self.cache = self.cache_next + self.coeff_next * p[1]
|
|
962
|
+
else:
|
|
963
|
+
self.cache = self.cache_next
|
|
964
|
+
|
|
965
|
+
cdef bint is_not_satisfied(Inequality_int self, int inner_loop_variable) noexcept:
|
|
966
|
+
return inner_loop_variable * self.coeff + self.cache < 0
|
|
967
|
+
|
|
968
|
+
cdef bint is_equality(Inequality_int self, int inner_loop_variable) noexcept:
|
|
969
|
+
return inner_loop_variable * self.coeff + self.cache == 0
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
cdef class InequalityCollection:
|
|
973
|
+
"""
|
|
974
|
+
A collection of inequalities.
|
|
975
|
+
|
|
976
|
+
INPUT:
|
|
977
|
+
|
|
978
|
+
- ``polyhedron`` -- a polyhedron defining the inequalities
|
|
979
|
+
|
|
980
|
+
- ``permutation`` -- list; a 0-based permutation of the coordinates
|
|
981
|
+
Will be used to permute the coordinates of the inequality
|
|
982
|
+
|
|
983
|
+
- ``box_min``, ``box_max`` -- the (not permuted) minimal and maximal
|
|
984
|
+
coordinates of the bounding box; used for bounds checking
|
|
985
|
+
|
|
986
|
+
EXAMPLES::
|
|
987
|
+
|
|
988
|
+
sage: from sage.geometry.integral_points import InequalityCollection
|
|
989
|
+
sage: P_QQ = Polyhedron(identity_matrix(3).columns() + [(-2, -1,-1)], base_ring=QQ)
|
|
990
|
+
sage: ieq = InequalityCollection(P_QQ, [0,1,2], [0]*3,[1]*3); ieq
|
|
991
|
+
The collection of inequalities
|
|
992
|
+
integer: (3, -2, -2) x + 2 >= 0
|
|
993
|
+
integer: (-1, 4, -1) x + 1 >= 0
|
|
994
|
+
integer: (-1, -1, 4) x + 1 >= 0
|
|
995
|
+
integer: (-1, -1, -1) x + 1 >= 0
|
|
996
|
+
|
|
997
|
+
sage: P_RR = Polyhedron(identity_matrix(2).columns() + [(-2.7, -1)], base_ring=RDF)
|
|
998
|
+
sage: InequalityCollection(P_RR, [0,1], [0]*2, [1]*2)
|
|
999
|
+
The collection of inequalities
|
|
1000
|
+
integer: (-1, -1) x + 1 >= 0
|
|
1001
|
+
generic: (-1.0, 3.7) x + 1.0 >= 0
|
|
1002
|
+
generic: (1.0, -1.35) x + 1.35 >= 0
|
|
1003
|
+
|
|
1004
|
+
sage: line = Polyhedron(eqns=[(2,3,7)])
|
|
1005
|
+
sage: InequalityCollection(line, [0,1], [0]*2, [1]*2 )
|
|
1006
|
+
The collection of inequalities
|
|
1007
|
+
integer: (3, 7) x + 2 >= 0
|
|
1008
|
+
integer: (-3, -7) x + -2 >= 0
|
|
1009
|
+
|
|
1010
|
+
TESTS::
|
|
1011
|
+
|
|
1012
|
+
sage: TestSuite(ieq).run(skip='_test_pickling')
|
|
1013
|
+
"""
|
|
1014
|
+
cdef list ineqs_int
|
|
1015
|
+
cdef list ineqs_generic
|
|
1016
|
+
|
|
1017
|
+
def __repr__(self):
|
|
1018
|
+
r"""
|
|
1019
|
+
Return a string representation.
|
|
1020
|
+
|
|
1021
|
+
OUTPUT: string
|
|
1022
|
+
|
|
1023
|
+
EXAMPLES::
|
|
1024
|
+
|
|
1025
|
+
sage: from sage.geometry.integral_points import InequalityCollection
|
|
1026
|
+
sage: line = Polyhedron(eqns=[(2,3,7)])
|
|
1027
|
+
sage: InequalityCollection(line, [0,1], [0]*2, [1]*2 ).__repr__()
|
|
1028
|
+
'The collection of inequalities\ninteger: (3, 7) x + 2 >= 0\ninteger: (-3, -7) x + -2 >= 0'
|
|
1029
|
+
"""
|
|
1030
|
+
s = 'The collection of inequalities\n'
|
|
1031
|
+
for ineq in self.ineqs_int:
|
|
1032
|
+
s += str(<Inequality_int>ineq) + '\n'
|
|
1033
|
+
for ineq in self.ineqs_generic:
|
|
1034
|
+
s += str(<Inequality_generic>ineq) + '\n'
|
|
1035
|
+
return s.strip()
|
|
1036
|
+
|
|
1037
|
+
cpdef tuple _make_A_b(self, Hrep_obj, list permutation):
|
|
1038
|
+
r"""
|
|
1039
|
+
Return the coefficients and constant of the H-representation
|
|
1040
|
+
object.
|
|
1041
|
+
|
|
1042
|
+
INPUT:
|
|
1043
|
+
|
|
1044
|
+
- ``Hrep_obj`` -- a H-representation object of the polyhedron
|
|
1045
|
+
- ``permutation`` -- the permutation of the coordinates to
|
|
1046
|
+
apply to ``A``
|
|
1047
|
+
|
|
1048
|
+
OUTPUT:
|
|
1049
|
+
|
|
1050
|
+
A pair ``(A,b)``.
|
|
1051
|
+
|
|
1052
|
+
EXAMPLES::
|
|
1053
|
+
|
|
1054
|
+
sage: from sage.geometry.integral_points import InequalityCollection
|
|
1055
|
+
sage: line = Polyhedron(eqns=[(2,3,7)])
|
|
1056
|
+
sage: ieq = InequalityCollection(line, [0,1], [0]*2, [1]*2 )
|
|
1057
|
+
sage: ieq._make_A_b(line.Hrepresentation(0), [0,1])
|
|
1058
|
+
([3, 7], 2)
|
|
1059
|
+
sage: ieq._make_A_b(line.Hrepresentation(0), [1,0])
|
|
1060
|
+
([7, 3], 2)
|
|
1061
|
+
"""
|
|
1062
|
+
cdef list v = list(Hrep_obj)
|
|
1063
|
+
cdef list A = perm_action(permutation, v[1:])
|
|
1064
|
+
b = v[0]
|
|
1065
|
+
try:
|
|
1066
|
+
x = lcm([a.denominator() for a in A] + [b.denominator()])
|
|
1067
|
+
A = [a * x for a in A]
|
|
1068
|
+
b = b * x
|
|
1069
|
+
except AttributeError:
|
|
1070
|
+
pass
|
|
1071
|
+
return (A, b)
|
|
1072
|
+
|
|
1073
|
+
def __cinit__(self, polyhedron, list permutation, box_min, box_max):
|
|
1074
|
+
"""
|
|
1075
|
+
The Cython constructor.
|
|
1076
|
+
|
|
1077
|
+
See the class documentation for the description of the arguments.
|
|
1078
|
+
|
|
1079
|
+
EXAMPLES::
|
|
1080
|
+
|
|
1081
|
+
sage: from sage.geometry.integral_points import InequalityCollection
|
|
1082
|
+
sage: line = Polyhedron(eqns=[(2,3,7)])
|
|
1083
|
+
sage: InequalityCollection(line, [0,1], [0]*2, [1]*2 )
|
|
1084
|
+
The collection of inequalities
|
|
1085
|
+
integer: (3, 7) x + 2 >= 0
|
|
1086
|
+
integer: (-3, -7) x + -2 >= 0
|
|
1087
|
+
"""
|
|
1088
|
+
cdef list max_abs_coordinates = [max(abs(c_min), abs(c_max))
|
|
1089
|
+
for c_min, c_max in zip(box_min, box_max)]
|
|
1090
|
+
max_abs_coordinates = perm_action(permutation, max_abs_coordinates)
|
|
1091
|
+
self.ineqs_int = []
|
|
1092
|
+
self.ineqs_generic = []
|
|
1093
|
+
if polyhedron is None:
|
|
1094
|
+
return
|
|
1095
|
+
|
|
1096
|
+
try:
|
|
1097
|
+
# polyhedron is a PPL C_Polyhedron class?
|
|
1098
|
+
self._cinit_from_PPL(max_abs_coordinates, permutation, polyhedron)
|
|
1099
|
+
except AttributeError:
|
|
1100
|
+
try:
|
|
1101
|
+
# polyhedron is a Polyhedron class?
|
|
1102
|
+
self._cinit_from_Polyhedron(max_abs_coordinates, permutation, polyhedron)
|
|
1103
|
+
except AttributeError:
|
|
1104
|
+
raise TypeError('Cannot extract Hrepresentation data from polyhedron.')
|
|
1105
|
+
|
|
1106
|
+
cdef _cinit_from_PPL(self, list max_abs_coordinates, list permutation,
|
|
1107
|
+
polyhedron):
|
|
1108
|
+
"""
|
|
1109
|
+
Initialize the inequalities from a PPL C_Polyhedron.
|
|
1110
|
+
|
|
1111
|
+
See __cinit__ for a description of the arguments.
|
|
1112
|
+
|
|
1113
|
+
EXAMPLES::
|
|
1114
|
+
|
|
1115
|
+
sage: # needs pplpy
|
|
1116
|
+
sage: from ppl import Variable, Generator_System, C_Polyhedron, point
|
|
1117
|
+
sage: gs = Generator_System()
|
|
1118
|
+
sage: x = Variable(0); y = Variable(1); z = Variable(2)
|
|
1119
|
+
sage: gs.insert(point(0*x + 0*y + 1*z))
|
|
1120
|
+
sage: gs.insert(point(0*x + 3*y + 1*z))
|
|
1121
|
+
sage: gs.insert(point(3*x + 0*y + 1*z))
|
|
1122
|
+
sage: gs.insert(point(3*x + 3*y + 1*z))
|
|
1123
|
+
sage: poly = C_Polyhedron(gs)
|
|
1124
|
+
sage: from sage.geometry.integral_points import InequalityCollection
|
|
1125
|
+
sage: InequalityCollection(poly, [0,2,1], [0]*3, [3]*3 )
|
|
1126
|
+
The collection of inequalities
|
|
1127
|
+
integer: (0, 1, 0) x + -1 >= 0
|
|
1128
|
+
integer: (0, -1, 0) x + 1 >= 0
|
|
1129
|
+
integer: (1, 0, 0) x + 0 >= 0
|
|
1130
|
+
integer: (0, 0, 1) x + 0 >= 0
|
|
1131
|
+
integer: (-1, 0, 0) x + 3 >= 0
|
|
1132
|
+
integer: (0, 0, -1) x + 3 >= 0
|
|
1133
|
+
"""
|
|
1134
|
+
cdef list A
|
|
1135
|
+
cdef int index
|
|
1136
|
+
for index,c in enumerate(polyhedron.minimized_constraints()):
|
|
1137
|
+
A = perm_action(permutation, [Integer(mpz) for mpz in c.coefficients()])
|
|
1138
|
+
b = Integer(c.inhomogeneous_term())
|
|
1139
|
+
try:
|
|
1140
|
+
H = Inequality_int(A, b, max_abs_coordinates, index)
|
|
1141
|
+
self.ineqs_int.append(H)
|
|
1142
|
+
except (OverflowError, ValueError):
|
|
1143
|
+
H = Inequality_generic(A, b, index)
|
|
1144
|
+
self.ineqs_generic.append(H)
|
|
1145
|
+
if c.is_equality():
|
|
1146
|
+
A = [ -a for a in A ]
|
|
1147
|
+
b = -b
|
|
1148
|
+
try:
|
|
1149
|
+
H = Inequality_int(A, b, max_abs_coordinates, index)
|
|
1150
|
+
self.ineqs_int.append(H)
|
|
1151
|
+
except (OverflowError, ValueError):
|
|
1152
|
+
H = Inequality_generic(A, b, index)
|
|
1153
|
+
self.ineqs_generic.append(H)
|
|
1154
|
+
|
|
1155
|
+
cdef _cinit_from_Polyhedron(self, list max_abs_coordinates,
|
|
1156
|
+
list permutation, polyhedron):
|
|
1157
|
+
"""
|
|
1158
|
+
Initialize the inequalities from a Sage Polyhedron.
|
|
1159
|
+
|
|
1160
|
+
See __cinit__ for a description of the arguments.
|
|
1161
|
+
|
|
1162
|
+
EXAMPLES::
|
|
1163
|
+
|
|
1164
|
+
sage: from sage.geometry.integral_points import InequalityCollection
|
|
1165
|
+
sage: line = Polyhedron(eqns=[(2,3,7)])
|
|
1166
|
+
sage: InequalityCollection(line, [0,1], [0]*2, [1]*2 )
|
|
1167
|
+
The collection of inequalities
|
|
1168
|
+
integer: (3, 7) x + 2 >= 0
|
|
1169
|
+
integer: (-3, -7) x + -2 >= 0
|
|
1170
|
+
|
|
1171
|
+
TESTS:
|
|
1172
|
+
|
|
1173
|
+
Check that :issue:`21037` is fixed::
|
|
1174
|
+
|
|
1175
|
+
sage: P = Polyhedron(vertices=((0, 0), (17,3)))
|
|
1176
|
+
sage: P += 1/1000*polytopes.regular_polygon(5) # needs sage.rings.number_field
|
|
1177
|
+
sage: P.integral_points() # needs sage.rings.number_field
|
|
1178
|
+
((0, 0), (17, 3))
|
|
1179
|
+
"""
|
|
1180
|
+
cdef list A
|
|
1181
|
+
for Hrep_obj in polyhedron.inequality_generator():
|
|
1182
|
+
A, b = self._make_A_b(Hrep_obj, permutation)
|
|
1183
|
+
try:
|
|
1184
|
+
H = Inequality_int(A, b, max_abs_coordinates, Hrep_obj.index())
|
|
1185
|
+
self.ineqs_int.append(H)
|
|
1186
|
+
except (OverflowError, ValueError, TypeError):
|
|
1187
|
+
H = Inequality_generic(A, b, Hrep_obj.index())
|
|
1188
|
+
self.ineqs_generic.append(H)
|
|
1189
|
+
for Hrep_obj in polyhedron.equation_generator():
|
|
1190
|
+
A, b = self._make_A_b(Hrep_obj, permutation)
|
|
1191
|
+
# add inequality
|
|
1192
|
+
try:
|
|
1193
|
+
H = Inequality_int(A, b, max_abs_coordinates, Hrep_obj.index())
|
|
1194
|
+
self.ineqs_int.append(H)
|
|
1195
|
+
except (OverflowError, ValueError, TypeError):
|
|
1196
|
+
H = Inequality_generic(A, b, Hrep_obj.index())
|
|
1197
|
+
self.ineqs_generic.append(H)
|
|
1198
|
+
# add sign-reversed inequality
|
|
1199
|
+
A = [ -a for a in A ]
|
|
1200
|
+
b = -b
|
|
1201
|
+
try:
|
|
1202
|
+
H = Inequality_int(A, b, max_abs_coordinates, Hrep_obj.index())
|
|
1203
|
+
self.ineqs_int.append(H)
|
|
1204
|
+
except (OverflowError, ValueError, TypeError):
|
|
1205
|
+
H = Inequality_generic(A, b, Hrep_obj.index())
|
|
1206
|
+
self.ineqs_generic.append(H)
|
|
1207
|
+
|
|
1208
|
+
cpdef prepare_next_to_inner_loop(self, p):
|
|
1209
|
+
r"""
|
|
1210
|
+
Peel off the next-to-inner loop.
|
|
1211
|
+
|
|
1212
|
+
In the next-to-inner loop of :func:`rectangular_box_points`,
|
|
1213
|
+
we have to repeatedly evaluate `A x-A_0 x_0+b`. To speed up
|
|
1214
|
+
computation, we pre-evaluate
|
|
1215
|
+
|
|
1216
|
+
.. MATH::
|
|
1217
|
+
|
|
1218
|
+
c = b + \sum_{i=2} A_i x_i
|
|
1219
|
+
|
|
1220
|
+
and only compute `A x-A_0 x_0+b = A_1 x_1 +c \geq 0` in the
|
|
1221
|
+
next-to-inner loop.
|
|
1222
|
+
|
|
1223
|
+
INPUT:
|
|
1224
|
+
|
|
1225
|
+
- ``p`` -- the point coordinates. Only ``p[2:]`` coordinates
|
|
1226
|
+
are potentially used by this method
|
|
1227
|
+
|
|
1228
|
+
EXAMPLES::
|
|
1229
|
+
|
|
1230
|
+
sage: from sage.geometry.integral_points import InequalityCollection, print_cache
|
|
1231
|
+
sage: P = Polyhedron(ieqs=[(2,3,7,11)])
|
|
1232
|
+
sage: ieq = InequalityCollection(P, [0,1,2], [0]*3,[1]*3); ieq
|
|
1233
|
+
The collection of inequalities
|
|
1234
|
+
integer: (3, 7, 11) x + 2 >= 0
|
|
1235
|
+
sage: ieq.prepare_next_to_inner_loop([2,1,3])
|
|
1236
|
+
sage: ieq.prepare_inner_loop([2,1,3])
|
|
1237
|
+
sage: print_cache(ieq)
|
|
1238
|
+
Cached inner loop: 3 * x_0 + 42 >= 0
|
|
1239
|
+
Cached next-to-inner loop: 3 * x_0 + 7 * x_1 + 35 >= 0
|
|
1240
|
+
"""
|
|
1241
|
+
for ineq in self.ineqs_int:
|
|
1242
|
+
(<Inequality_int>ineq).prepare_next_to_inner_loop(p)
|
|
1243
|
+
for ineq in self.ineqs_generic:
|
|
1244
|
+
(<Inequality_generic>ineq).prepare_next_to_inner_loop(p)
|
|
1245
|
+
|
|
1246
|
+
cpdef prepare_inner_loop(self, p):
|
|
1247
|
+
r"""
|
|
1248
|
+
Peel off the inner loop.
|
|
1249
|
+
|
|
1250
|
+
In the inner loop of :func:`rectangular_box_points`, we have
|
|
1251
|
+
to repeatedly evaluate `A x+b\geq 0`. To speed up computation, we pre-evaluate
|
|
1252
|
+
|
|
1253
|
+
.. MATH::
|
|
1254
|
+
|
|
1255
|
+
c = A x - A_0 x_0 +b = b + \sum_{i=1} A_i x_i
|
|
1256
|
+
|
|
1257
|
+
and only test `A_0 x_0 +c \geq 0` in the inner loop.
|
|
1258
|
+
|
|
1259
|
+
You must call :meth:`prepare_next_to_inner_loop` before
|
|
1260
|
+
calling this method.
|
|
1261
|
+
|
|
1262
|
+
INPUT:
|
|
1263
|
+
|
|
1264
|
+
- ``p`` -- the coordinates of the point to loop over. Only the
|
|
1265
|
+
``p[1:]`` entries are used
|
|
1266
|
+
|
|
1267
|
+
EXAMPLES::
|
|
1268
|
+
|
|
1269
|
+
sage: from sage.geometry.integral_points import InequalityCollection, print_cache
|
|
1270
|
+
sage: P = Polyhedron(ieqs=[(2,3,7,11)])
|
|
1271
|
+
sage: ieq = InequalityCollection(P, [0,1,2], [0]*3,[1]*3); ieq
|
|
1272
|
+
The collection of inequalities
|
|
1273
|
+
integer: (3, 7, 11) x + 2 >= 0
|
|
1274
|
+
sage: ieq.prepare_next_to_inner_loop([2,1,3])
|
|
1275
|
+
sage: ieq.prepare_inner_loop([2,1,3])
|
|
1276
|
+
sage: print_cache(ieq)
|
|
1277
|
+
Cached inner loop: 3 * x_0 + 42 >= 0
|
|
1278
|
+
Cached next-to-inner loop: 3 * x_0 + 7 * x_1 + 35 >= 0
|
|
1279
|
+
"""
|
|
1280
|
+
for ineq in self.ineqs_int:
|
|
1281
|
+
(<Inequality_int>ineq).prepare_inner_loop(p)
|
|
1282
|
+
for ineq in self.ineqs_generic:
|
|
1283
|
+
(<Inequality_generic>ineq).prepare_inner_loop(p)
|
|
1284
|
+
|
|
1285
|
+
cpdef swap_ineq_to_front(self, int i):
|
|
1286
|
+
r"""
|
|
1287
|
+
Swap the ``i``-th entry of the list to the front of the list of inequalities.
|
|
1288
|
+
|
|
1289
|
+
INPUT:
|
|
1290
|
+
|
|
1291
|
+
- ``i`` -- integer; the :class:`Inequality_int` to swap to the
|
|
1292
|
+
beginning of the list of integral inequalities
|
|
1293
|
+
|
|
1294
|
+
EXAMPLES::
|
|
1295
|
+
|
|
1296
|
+
sage: from sage.geometry.integral_points import InequalityCollection
|
|
1297
|
+
sage: P_QQ = Polyhedron(identity_matrix(3).columns() + [(-2, -1,-1)], base_ring=QQ)
|
|
1298
|
+
sage: iec = InequalityCollection(P_QQ, [0,1,2], [0]*3,[1]*3)
|
|
1299
|
+
sage: iec
|
|
1300
|
+
The collection of inequalities
|
|
1301
|
+
integer: (3, -2, -2) x + 2 >= 0
|
|
1302
|
+
integer: (-1, 4, -1) x + 1 >= 0
|
|
1303
|
+
integer: (-1, -1, 4) x + 1 >= 0
|
|
1304
|
+
integer: (-1, -1, -1) x + 1 >= 0
|
|
1305
|
+
sage: iec.swap_ineq_to_front(3)
|
|
1306
|
+
sage: iec
|
|
1307
|
+
The collection of inequalities
|
|
1308
|
+
integer: (-1, -1, -1) x + 1 >= 0
|
|
1309
|
+
integer: (3, -2, -2) x + 2 >= 0
|
|
1310
|
+
integer: (-1, 4, -1) x + 1 >= 0
|
|
1311
|
+
integer: (-1, -1, 4) x + 1 >= 0
|
|
1312
|
+
"""
|
|
1313
|
+
i_th_entry = self.ineqs_int[i]
|
|
1314
|
+
cdef int j
|
|
1315
|
+
for j in range(i-1,-1,-1):
|
|
1316
|
+
self.ineqs_int[j+1] = self.ineqs_int[j]
|
|
1317
|
+
self.ineqs_int[0] = i_th_entry
|
|
1318
|
+
|
|
1319
|
+
cpdef bint are_satisfied(self, inner_loop_variable) except -1:
|
|
1320
|
+
r"""
|
|
1321
|
+
Return whether all inequalities are satisfied.
|
|
1322
|
+
|
|
1323
|
+
You must call :meth:`prepare_inner_loop` before calling this
|
|
1324
|
+
method.
|
|
1325
|
+
|
|
1326
|
+
INPUT:
|
|
1327
|
+
|
|
1328
|
+
- ``inner_loop_variable`` -- integer; the 0th coordinate of
|
|
1329
|
+
the lattice point
|
|
1330
|
+
|
|
1331
|
+
OUTPUT: boolean; whether the lattice point is in the polyhedron
|
|
1332
|
+
|
|
1333
|
+
EXAMPLES::
|
|
1334
|
+
|
|
1335
|
+
sage: from sage.geometry.integral_points import InequalityCollection
|
|
1336
|
+
sage: line = Polyhedron(eqns=[(2,3,7)])
|
|
1337
|
+
sage: ieq = InequalityCollection(line, [0,1], [0]*2, [1]*2 )
|
|
1338
|
+
sage: ieq.prepare_next_to_inner_loop([3,4])
|
|
1339
|
+
sage: ieq.prepare_inner_loop([3,4])
|
|
1340
|
+
sage: ieq.are_satisfied(3)
|
|
1341
|
+
False
|
|
1342
|
+
"""
|
|
1343
|
+
cdef int i
|
|
1344
|
+
for i in range(len(self.ineqs_int)):
|
|
1345
|
+
sig_check()
|
|
1346
|
+
ineq = self.ineqs_int[i]
|
|
1347
|
+
if (<Inequality_int>ineq).is_not_satisfied(inner_loop_variable):
|
|
1348
|
+
if i > 0:
|
|
1349
|
+
self.swap_ineq_to_front(i)
|
|
1350
|
+
return False
|
|
1351
|
+
for i in range(len(self.ineqs_generic)):
|
|
1352
|
+
sig_check()
|
|
1353
|
+
ineq = self.ineqs_generic[i]
|
|
1354
|
+
if (<Inequality_generic>ineq).is_not_satisfied(inner_loop_variable):
|
|
1355
|
+
return False
|
|
1356
|
+
return True
|
|
1357
|
+
|
|
1358
|
+
cpdef frozenset satisfied_as_equalities(self, inner_loop_variable):
|
|
1359
|
+
"""
|
|
1360
|
+
Return the inequalities (by their index) that are satisfied as
|
|
1361
|
+
equalities.
|
|
1362
|
+
|
|
1363
|
+
INPUT:
|
|
1364
|
+
|
|
1365
|
+
- ``inner_loop_variable`` -- integer; the 0th coordinate of
|
|
1366
|
+
the lattice point
|
|
1367
|
+
|
|
1368
|
+
OUTPUT:
|
|
1369
|
+
|
|
1370
|
+
A set of integers in ascending order. Each integer is the
|
|
1371
|
+
index of a H-representation object of the polyhedron (either a
|
|
1372
|
+
inequality or an equation).
|
|
1373
|
+
|
|
1374
|
+
EXAMPLES::
|
|
1375
|
+
|
|
1376
|
+
sage: from sage.geometry.integral_points import InequalityCollection
|
|
1377
|
+
sage: quadrant = Polyhedron(rays=[(1,0), (0,1)])
|
|
1378
|
+
sage: ieqs = InequalityCollection(quadrant, [0,1], [-1]*2, [1]*2 )
|
|
1379
|
+
sage: ieqs.prepare_next_to_inner_loop([-1,0])
|
|
1380
|
+
sage: ieqs.prepare_inner_loop([-1,0])
|
|
1381
|
+
sage: ieqs.satisfied_as_equalities(-1)
|
|
1382
|
+
frozenset({1})
|
|
1383
|
+
sage: ieqs.satisfied_as_equalities(0)
|
|
1384
|
+
frozenset({0, 1})
|
|
1385
|
+
sage: ieqs.satisfied_as_equalities(1)
|
|
1386
|
+
frozenset({1})
|
|
1387
|
+
"""
|
|
1388
|
+
cdef int i
|
|
1389
|
+
cdef list result = []
|
|
1390
|
+
for i in range(len(self.ineqs_int)):
|
|
1391
|
+
sig_check()
|
|
1392
|
+
ineq = self.ineqs_int[i]
|
|
1393
|
+
if (<Inequality_int>ineq).is_equality(inner_loop_variable):
|
|
1394
|
+
result.append( (<Inequality_int>ineq).index )
|
|
1395
|
+
for i in range(len(self.ineqs_generic)):
|
|
1396
|
+
sig_check()
|
|
1397
|
+
ineq = self.ineqs_generic[i]
|
|
1398
|
+
if (<Inequality_generic>ineq).is_equality(inner_loop_variable):
|
|
1399
|
+
result.append( (<Inequality_generic>ineq).index )
|
|
1400
|
+
return frozenset(result)
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
cpdef print_cache(InequalityCollection inequality_collection):
|
|
1404
|
+
r"""
|
|
1405
|
+
Print the cached values in :class:`Inequality_int` (for
|
|
1406
|
+
debugging/doctesting only).
|
|
1407
|
+
|
|
1408
|
+
EXAMPLES::
|
|
1409
|
+
|
|
1410
|
+
sage: from sage.geometry.integral_points import InequalityCollection, print_cache
|
|
1411
|
+
sage: P = Polyhedron(ieqs=[(2,3,7)])
|
|
1412
|
+
sage: ieq = InequalityCollection(P, [0,1], [0]*2,[1]*2); ieq
|
|
1413
|
+
The collection of inequalities
|
|
1414
|
+
integer: (3, 7) x + 2 >= 0
|
|
1415
|
+
sage: ieq.prepare_next_to_inner_loop([3,5])
|
|
1416
|
+
sage: ieq.prepare_inner_loop([3,5])
|
|
1417
|
+
sage: print_cache(ieq)
|
|
1418
|
+
Cached inner loop: 3 * x_0 + 37 >= 0
|
|
1419
|
+
Cached next-to-inner loop: 3 * x_0 + 7 * x_1 + 2 >= 0
|
|
1420
|
+
"""
|
|
1421
|
+
cdef Inequality_int ieq = <Inequality_int>(inequality_collection.ineqs_int[0])
|
|
1422
|
+
print('Cached inner loop: ' +
|
|
1423
|
+
str(ieq.coeff) + ' * x_0 + ' + str(ieq.cache) + ' >= 0')
|
|
1424
|
+
print('Cached next-to-inner loop: ' +
|
|
1425
|
+
str(ieq.coeff) + ' * x_0 + ' +
|
|
1426
|
+
str(ieq.coeff_next) + ' * x_1 + ' + str(ieq.cache_next) + ' >= 0')
|