certlin 1.1__py3-none-any.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.
- certlin/__init__.py +20 -0
- certlin/intervals.py +654 -0
- certlin/linear_inequality_systems.py +646 -0
- certlin/utility.py +93 -0
- certlin-1.1.dist-info/METADATA +113 -0
- certlin-1.1.dist-info/RECORD +9 -0
- certlin-1.1.dist-info/WHEEL +5 -0
- certlin-1.1.dist-info/licenses/LICENSE +674 -0
- certlin-1.1.dist-info/top_level.txt +1 -0
certlin/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
r"""Certifying linear inequality systems"""
|
|
2
|
+
|
|
3
|
+
#############################################################################
|
|
4
|
+
# Copyright (C) 2025 #
|
|
5
|
+
# Marcus S. Aichmayr (aichmayr@mathematik.uni-kassel.de) #
|
|
6
|
+
# #
|
|
7
|
+
# Distributed under the terms of the GNU General Public License (GPL) #
|
|
8
|
+
# either version 3, or (at your option) any later version #
|
|
9
|
+
# #
|
|
10
|
+
# http://www.gnu.org/licenses/ #
|
|
11
|
+
#############################################################################
|
|
12
|
+
|
|
13
|
+
from __future__ import absolute_import
|
|
14
|
+
|
|
15
|
+
from .intervals import Interval, Intervals
|
|
16
|
+
from .linear_inequality_systems import (
|
|
17
|
+
LinearInequalitySystem,
|
|
18
|
+
InhomogeneousSystem,
|
|
19
|
+
HomogeneousSystem,
|
|
20
|
+
)
|
certlin/intervals.py
ADDED
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
"""Interval classes"""
|
|
2
|
+
|
|
3
|
+
#############################################################################
|
|
4
|
+
# Copyright (C) 2025 #
|
|
5
|
+
# Marcus S. Aichmayr (aichmayr@mathematik.uni-kassel.de) #
|
|
6
|
+
# #
|
|
7
|
+
# Distributed under the terms of the GNU General Public License (GPL) #
|
|
8
|
+
# either version 3, or (at your option) any later version #
|
|
9
|
+
# #
|
|
10
|
+
# http://www.gnu.org/licenses/ #
|
|
11
|
+
#############################################################################
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Iterator
|
|
16
|
+
from random import getrandbits
|
|
17
|
+
|
|
18
|
+
from sage.functions.other import floor, ceil
|
|
19
|
+
from sage.misc.mrange import cartesian_product_iterator
|
|
20
|
+
from sage.rings.continued_fraction import continued_fraction
|
|
21
|
+
from sage.rings.infinity import minus_infinity
|
|
22
|
+
from sage.rings.infinity import Infinity
|
|
23
|
+
from sage.rings.rational_field import QQ
|
|
24
|
+
from sage.structure.sage_object import SageObject
|
|
25
|
+
|
|
26
|
+
from sign_vectors import SignVector, sign_vector
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Interval(SageObject):
|
|
30
|
+
r"""
|
|
31
|
+
An interval.
|
|
32
|
+
|
|
33
|
+
Also supports variables.
|
|
34
|
+
|
|
35
|
+
EXAMPLES::
|
|
36
|
+
|
|
37
|
+
sage: from certlin import *
|
|
38
|
+
sage: Interval(0, 1)
|
|
39
|
+
[0, 1]
|
|
40
|
+
sage: Interval(0, 1, False, True)
|
|
41
|
+
(0, 1]
|
|
42
|
+
sage: Interval(0, 1, False, False)
|
|
43
|
+
(0, 1)
|
|
44
|
+
sage: Interval(0, 1, True, False)
|
|
45
|
+
[0, 1)
|
|
46
|
+
sage: Interval(0, 0)
|
|
47
|
+
{0}
|
|
48
|
+
sage: Interval(0, 0, False, False)
|
|
49
|
+
{}
|
|
50
|
+
sage: Interval(0, 0, True, True)
|
|
51
|
+
{0}
|
|
52
|
+
sage: Interval(0, 0, True, False)
|
|
53
|
+
{}
|
|
54
|
+
sage: Interval(0, 0, False, True)
|
|
55
|
+
{}
|
|
56
|
+
sage: Interval(-oo, 4)
|
|
57
|
+
(-oo, 4]
|
|
58
|
+
sage: Interval(-oo, 4, False, False)
|
|
59
|
+
(-oo, 4)
|
|
60
|
+
sage: I = Interval(-3, +oo, False, False)
|
|
61
|
+
sage: I
|
|
62
|
+
(-3, +oo)
|
|
63
|
+
sage: 0 in I
|
|
64
|
+
True
|
|
65
|
+
sage: -3 in I
|
|
66
|
+
False
|
|
67
|
+
sage: Interval.random() # random
|
|
68
|
+
(-1, 1)
|
|
69
|
+
|
|
70
|
+
Variables are supported, too::
|
|
71
|
+
|
|
72
|
+
sage: var("a")
|
|
73
|
+
a
|
|
74
|
+
sage: Interval(a, 1)
|
|
75
|
+
[a, 1]
|
|
76
|
+
sage: I = Interval(a, 1, False, False)
|
|
77
|
+
sage: I
|
|
78
|
+
(a, 1)
|
|
79
|
+
sage: I.an_element()
|
|
80
|
+
1/2*a + 1/2
|
|
81
|
+
"""
|
|
82
|
+
def __init__(self, lower, upper, lower_closed: bool = True, upper_closed: bool = True) -> None:
|
|
83
|
+
if lower > upper:
|
|
84
|
+
raise ValueError("The lower bound must be less than or equal to the upper bound.")
|
|
85
|
+
|
|
86
|
+
if lower == upper and (not lower_closed or not upper_closed):
|
|
87
|
+
lower = 0
|
|
88
|
+
upper = 0
|
|
89
|
+
lower_closed = False
|
|
90
|
+
upper_closed = False
|
|
91
|
+
|
|
92
|
+
self.lower = lower
|
|
93
|
+
self.upper = upper
|
|
94
|
+
if lower == minus_infinity:
|
|
95
|
+
lower_closed = False
|
|
96
|
+
if upper == Infinity:
|
|
97
|
+
upper_closed = False
|
|
98
|
+
self.lower_closed = lower_closed
|
|
99
|
+
self.upper_closed = upper_closed
|
|
100
|
+
|
|
101
|
+
def __contains__(self, value) -> bool:
|
|
102
|
+
if self.lower_closed and value == self.lower:
|
|
103
|
+
return True
|
|
104
|
+
if self.upper_closed and value == self.upper:
|
|
105
|
+
return True
|
|
106
|
+
return self.lower < value < self.upper
|
|
107
|
+
|
|
108
|
+
def _repr_(self) -> str:
|
|
109
|
+
if self.is_empty():
|
|
110
|
+
return "{}"
|
|
111
|
+
if self.is_pointed():
|
|
112
|
+
return f"{{{self.lower}}}"
|
|
113
|
+
return f"{'[' if self.lower_closed else '('}{'-oo' if self.lower == minus_infinity else self.lower}, {'+oo' if self.upper == Infinity else self.upper}{']' if self.upper_closed else ')'}"
|
|
114
|
+
|
|
115
|
+
def _latex_(self) -> str:
|
|
116
|
+
r"""
|
|
117
|
+
Return a LaTeX representation of the interval.
|
|
118
|
+
|
|
119
|
+
EXAMPLES::
|
|
120
|
+
sage: from certlin import *
|
|
121
|
+
sage: Interval(0, 1)._latex_()
|
|
122
|
+
'[0, 1]'
|
|
123
|
+
sage: Interval(0, 1, False, True)._latex_()
|
|
124
|
+
'(0, 1]'
|
|
125
|
+
sage: Interval(0, 0, False, False)._latex_()
|
|
126
|
+
'\\emptyset'
|
|
127
|
+
sage: Interval(0, 0)._latex_()
|
|
128
|
+
'\\{0\\}'
|
|
129
|
+
sage: Interval(-oo, 1/2)._latex_()
|
|
130
|
+
'(-\\infty, 1/2]'
|
|
131
|
+
sage: Interval(0, oo, False, False)._latex_()
|
|
132
|
+
'(0, \\infty)'
|
|
133
|
+
"""
|
|
134
|
+
if self.is_empty():
|
|
135
|
+
return r"\emptyset"
|
|
136
|
+
if self.is_pointed():
|
|
137
|
+
return r"\{" + f"{self.lower}" + r"\}"
|
|
138
|
+
return (
|
|
139
|
+
("[" if self.lower_closed else "(")
|
|
140
|
+
+ (r"-\infty" if self.lower == minus_infinity else f"{self.lower}")
|
|
141
|
+
+ ", "
|
|
142
|
+
+ (r"\infty" if self.upper == Infinity else f"{self.upper}")
|
|
143
|
+
+ ("]" if self.upper_closed else ")")
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def __eq__(self, other) -> bool:
|
|
147
|
+
return self.lower == other.lower and self.upper == other.upper and self.lower_closed == other.lower_closed and self.upper_closed == other.upper_closed
|
|
148
|
+
|
|
149
|
+
def is_empty(self) -> bool:
|
|
150
|
+
r"""Return whether the interval is empty."""
|
|
151
|
+
return self.lower == self.upper and (not self.lower_closed or not self.upper_closed)
|
|
152
|
+
|
|
153
|
+
def is_open(self) -> bool:
|
|
154
|
+
r"""Return whether the interval is open."""
|
|
155
|
+
return self.lower_closed == self.upper_closed == False
|
|
156
|
+
|
|
157
|
+
def is_closed(self) -> bool:
|
|
158
|
+
r"""Return whether the interval is closed."""
|
|
159
|
+
return self.lower_closed and self.upper_closed
|
|
160
|
+
|
|
161
|
+
def is_unbounded(self) -> bool:
|
|
162
|
+
r"""Return whether the interval is unbounded."""
|
|
163
|
+
return self.lower == minus_infinity or self.upper == Infinity
|
|
164
|
+
|
|
165
|
+
def is_bounded(self) -> bool:
|
|
166
|
+
r"""Return whether the interval is bounded."""
|
|
167
|
+
return not self.is_unbounded()
|
|
168
|
+
|
|
169
|
+
def is_pointed(self) -> bool:
|
|
170
|
+
r"""
|
|
171
|
+
Return whether the interval is a point.
|
|
172
|
+
|
|
173
|
+
An interval is pointed if it has the same lower and upper bound, and both are closed.
|
|
174
|
+
|
|
175
|
+
EXAMPLES::
|
|
176
|
+
|
|
177
|
+
sage: from certlin import *
|
|
178
|
+
sage: Interval(0, 0).is_pointed()
|
|
179
|
+
True
|
|
180
|
+
sage: Interval(0, 1).is_pointed()
|
|
181
|
+
False
|
|
182
|
+
"""
|
|
183
|
+
return self.lower == self.upper and self.lower_closed and self.upper_closed
|
|
184
|
+
|
|
185
|
+
def __bool__(self) -> bool:
|
|
186
|
+
return not self.is_empty()
|
|
187
|
+
|
|
188
|
+
def infimum(self):
|
|
189
|
+
r"""
|
|
190
|
+
Return the infimum of the interval.
|
|
191
|
+
|
|
192
|
+
EXAMPLES::
|
|
193
|
+
|
|
194
|
+
sage: from certlin import *
|
|
195
|
+
sage: Interval(0, 1).infimum()
|
|
196
|
+
0
|
|
197
|
+
sage: Interval(-oo, 0).infimum()
|
|
198
|
+
-Infinity
|
|
199
|
+
sage: Interval(0, 0, False, False).infimum()
|
|
200
|
+
+Infinity
|
|
201
|
+
"""
|
|
202
|
+
if self.is_empty():
|
|
203
|
+
return Infinity
|
|
204
|
+
return self.lower
|
|
205
|
+
|
|
206
|
+
def supremum(self):
|
|
207
|
+
r"""
|
|
208
|
+
Return the supremum of the interval.
|
|
209
|
+
|
|
210
|
+
EXAMPLES::
|
|
211
|
+
|
|
212
|
+
sage: from certlin import *
|
|
213
|
+
sage: Interval(0, 1).supremum()
|
|
214
|
+
1
|
|
215
|
+
sage: Interval(0, +oo).supremum()
|
|
216
|
+
+Infinity
|
|
217
|
+
sage: Interval(0, 0, False, False).supremum()
|
|
218
|
+
-Infinity
|
|
219
|
+
"""
|
|
220
|
+
if self.is_empty():
|
|
221
|
+
return minus_infinity
|
|
222
|
+
return self.upper
|
|
223
|
+
|
|
224
|
+
def an_element(self):
|
|
225
|
+
r"""
|
|
226
|
+
Return an element of the interval.
|
|
227
|
+
|
|
228
|
+
EXAMPLES::
|
|
229
|
+
|
|
230
|
+
sage: from certlin import *
|
|
231
|
+
sage: Interval(0, 1).an_element()
|
|
232
|
+
0
|
|
233
|
+
sage: Interval(0, 1, False, True).an_element()
|
|
234
|
+
1
|
|
235
|
+
sage: Interval(0, 1, False, False).an_element()
|
|
236
|
+
1/2
|
|
237
|
+
sage: Interval(-oo, 0).an_element()
|
|
238
|
+
0
|
|
239
|
+
sage: Interval(-oo, +oo).an_element()
|
|
240
|
+
0
|
|
241
|
+
sage: Interval(5, +oo, False, False).an_element()
|
|
242
|
+
6
|
|
243
|
+
sage: Interval(0, 0, False, False).an_element()
|
|
244
|
+
Traceback (most recent call last):
|
|
245
|
+
...
|
|
246
|
+
ValueError: The interval is empty.
|
|
247
|
+
"""
|
|
248
|
+
if self.is_empty():
|
|
249
|
+
raise ValueError("The interval is empty.")
|
|
250
|
+
if self.lower_closed:
|
|
251
|
+
return self.lower
|
|
252
|
+
if self.upper_closed:
|
|
253
|
+
return self.upper
|
|
254
|
+
if self.is_bounded():
|
|
255
|
+
return (self.lower + self.upper) / 2
|
|
256
|
+
if self.lower == minus_infinity:
|
|
257
|
+
if self.upper == Infinity:
|
|
258
|
+
return 0
|
|
259
|
+
return self.upper - 1
|
|
260
|
+
return self.lower + 1
|
|
261
|
+
|
|
262
|
+
def simplest_element(self):
|
|
263
|
+
r"""
|
|
264
|
+
Return the simplest rational in this interval.
|
|
265
|
+
|
|
266
|
+
OUTPUT:
|
|
267
|
+
If possible, an integer with smallest possible absolute value will be returned.
|
|
268
|
+
Otherwise, a rational number with smallest possible denominator is constructed.
|
|
269
|
+
|
|
270
|
+
EXAMPLES::
|
|
271
|
+
|
|
272
|
+
sage: from certlin import *
|
|
273
|
+
sage: Interval(1/2, +oo, False, False).simplest_element()
|
|
274
|
+
1
|
|
275
|
+
sage: Interval(-oo, 1/2, False, False).simplest_element()
|
|
276
|
+
0
|
|
277
|
+
sage: Interval(-19, 0, False, False).simplest_element()
|
|
278
|
+
-1
|
|
279
|
+
sage: Interval(0, 1, False, False).simplest_element()
|
|
280
|
+
1/2
|
|
281
|
+
sage: Interval(-2/3, 0, False, False).simplest_element()
|
|
282
|
+
-1/2
|
|
283
|
+
sage: Interval(4/3, 3/2, False, False).simplest_element()
|
|
284
|
+
7/5
|
|
285
|
+
sage: Interval(0, 0).simplest_element()
|
|
286
|
+
0
|
|
287
|
+
sage: Interval(5, 5).simplest_element()
|
|
288
|
+
5
|
|
289
|
+
sage: Interval(sqrt(2), pi/2).simplest_element()
|
|
290
|
+
3/2
|
|
291
|
+
sage: Interval(1/2, 1/2).simplest_element()
|
|
292
|
+
1/2
|
|
293
|
+
"""
|
|
294
|
+
if self.is_empty():
|
|
295
|
+
raise ValueError("The interval is empty.")
|
|
296
|
+
|
|
297
|
+
if self.is_pointed():
|
|
298
|
+
return self.lower
|
|
299
|
+
if 0 in self:
|
|
300
|
+
return 0
|
|
301
|
+
if self.upper - self.lower > 1 or floor(self.lower) + 1 in self or ceil(self.upper) - 1 in self:
|
|
302
|
+
if self.lower == minus_infinity:
|
|
303
|
+
floor_upper = floor(self.upper)
|
|
304
|
+
return floor_upper if floor_upper in self else floor_upper - 1
|
|
305
|
+
if self.upper == Infinity:
|
|
306
|
+
ceil_lower = ceil(self.lower)
|
|
307
|
+
return ceil_lower if ceil_lower in self else ceil_lower + 1
|
|
308
|
+
if self.lower == 0:
|
|
309
|
+
return 1
|
|
310
|
+
if self.upper == 0:
|
|
311
|
+
return -1
|
|
312
|
+
if self.upper < 0:
|
|
313
|
+
floor_upper = floor(self.upper)
|
|
314
|
+
return floor_upper if floor_upper in self else floor_upper - 1
|
|
315
|
+
# self.lower > 0
|
|
316
|
+
ceil_lower = ceil(self.lower)
|
|
317
|
+
return ceil_lower if ceil_lower in self else ceil_lower + 1
|
|
318
|
+
return self._simplest_rational()
|
|
319
|
+
|
|
320
|
+
def _simplest_rational(self):
|
|
321
|
+
r"""
|
|
322
|
+
Find the rational with smallest denominator in a given interval.
|
|
323
|
+
|
|
324
|
+
INPUT:
|
|
325
|
+
|
|
326
|
+
- ``interval`` -- an interval that has no integer in it.
|
|
327
|
+
|
|
328
|
+
EXAMPLES::
|
|
329
|
+
|
|
330
|
+
sage: from certlin import *
|
|
331
|
+
sage: Interval(0, 1, False, False)._simplest_rational()
|
|
332
|
+
1/2
|
|
333
|
+
sage: Interval(1/3, 1, False, False)._simplest_rational()
|
|
334
|
+
1/2
|
|
335
|
+
sage: Interval(4/3, 2, False, False)._simplest_rational()
|
|
336
|
+
3/2
|
|
337
|
+
sage: Interval(1/3, 1/2, False, False)._simplest_rational()
|
|
338
|
+
2/5
|
|
339
|
+
sage: Interval(1/2, 241/287, False, False)._simplest_rational()
|
|
340
|
+
2/3
|
|
341
|
+
"""
|
|
342
|
+
def sb_child(cfl: list, left: bool):
|
|
343
|
+
r"""
|
|
344
|
+
Return a child of an element in the Stern-Brocot tree.
|
|
345
|
+
|
|
346
|
+
INPUT:
|
|
347
|
+
|
|
348
|
+
- ``cfl`` -- a list corresponding to a continued fraction
|
|
349
|
+
- ``left`` -- a boolean
|
|
350
|
+
"""
|
|
351
|
+
if left != len(cfl) & 1: # check if odd
|
|
352
|
+
return cfl[:-1] + [cfl[-1] + 1]
|
|
353
|
+
return cfl[:-1] + [cfl[-1] - 1, 2]
|
|
354
|
+
|
|
355
|
+
cfl = [floor(self.infimum()), 2] # continued fraction representation of inf(interval) + 1/2
|
|
356
|
+
|
|
357
|
+
while True:
|
|
358
|
+
value = continued_fraction(cfl).value()
|
|
359
|
+
if value in self:
|
|
360
|
+
return value
|
|
361
|
+
if value <= self.infimum():
|
|
362
|
+
cfl = sb_child(cfl, left=False)
|
|
363
|
+
else:
|
|
364
|
+
cfl = sb_child(cfl, left=True)
|
|
365
|
+
|
|
366
|
+
@classmethod
|
|
367
|
+
def random(cls, ring=QQ, allow_infinity: bool = True, allow_empty: bool = False) -> Interval:
|
|
368
|
+
r"""
|
|
369
|
+
Generate a random interval.
|
|
370
|
+
|
|
371
|
+
EXAMPLES::
|
|
372
|
+
|
|
373
|
+
sage: from certlin import *
|
|
374
|
+
sage: Interval.random() # random
|
|
375
|
+
(-1, 1)
|
|
376
|
+
sage: Interval.random(ring=ZZ) # random
|
|
377
|
+
[-5, 3]
|
|
378
|
+
"""
|
|
379
|
+
if allow_infinity and getrandbits(3) == 0:
|
|
380
|
+
lower = minus_infinity
|
|
381
|
+
else:
|
|
382
|
+
lower = ring.random_element()
|
|
383
|
+
if allow_infinity and getrandbits(3) == 0:
|
|
384
|
+
upper = Infinity
|
|
385
|
+
else:
|
|
386
|
+
upper = ring.random_element()
|
|
387
|
+
if lower > upper:
|
|
388
|
+
lower, upper = upper, lower
|
|
389
|
+
lower_closed = bool(getrandbits(1))
|
|
390
|
+
upper_closed = bool(getrandbits(1))
|
|
391
|
+
|
|
392
|
+
interval = cls(lower, upper, lower_closed, upper_closed)
|
|
393
|
+
if not allow_empty and interval.is_empty():
|
|
394
|
+
return cls.random(ring, allow_infinity, allow_empty)
|
|
395
|
+
return interval
|
|
396
|
+
|
|
397
|
+
@classmethod
|
|
398
|
+
def open(cls, lower, upper) -> Interval:
|
|
399
|
+
r"""
|
|
400
|
+
Return an open interval.
|
|
401
|
+
|
|
402
|
+
EXAMPLES::
|
|
403
|
+
|
|
404
|
+
sage: from certlin import *
|
|
405
|
+
sage: Interval.open(0, 1)
|
|
406
|
+
(0, 1)
|
|
407
|
+
"""
|
|
408
|
+
return cls(lower, upper, False, False)
|
|
409
|
+
|
|
410
|
+
@classmethod
|
|
411
|
+
def closed(cls, lower, upper) -> Interval:
|
|
412
|
+
r"""
|
|
413
|
+
Return a closed interval.
|
|
414
|
+
|
|
415
|
+
EXAMPLES::
|
|
416
|
+
|
|
417
|
+
sage: from certlin import *
|
|
418
|
+
sage: Interval.closed(0, 1)
|
|
419
|
+
[0, 1]
|
|
420
|
+
"""
|
|
421
|
+
return cls(lower, upper, True, True)
|
|
422
|
+
|
|
423
|
+
@classmethod
|
|
424
|
+
def empty(cls) -> Interval:
|
|
425
|
+
r"""
|
|
426
|
+
Return the empty interval.
|
|
427
|
+
|
|
428
|
+
EXAMPLES::
|
|
429
|
+
|
|
430
|
+
sage: from certlin import *
|
|
431
|
+
sage: Interval.empty()
|
|
432
|
+
{}
|
|
433
|
+
"""
|
|
434
|
+
return cls(0, 0, False, False)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class Intervals(SageObject):
|
|
438
|
+
r"""
|
|
439
|
+
A Cartesian product of intervals.
|
|
440
|
+
|
|
441
|
+
EXAMPLES::
|
|
442
|
+
|
|
443
|
+
sage: from certlin import *
|
|
444
|
+
sage: Intervals([Interval(0, 1), Interval(-5, 2, False, False), Interval(0, 1)])
|
|
445
|
+
[0, 1] x (-5, 2) x [0, 1]
|
|
446
|
+
sage: vector([0, 1]) in Intervals([Interval(0, 1), Interval(-5, 2)])
|
|
447
|
+
True
|
|
448
|
+
sage: Intervals.random(3) # random
|
|
449
|
+
[0, +oo) x (-5, 2) x (0, 1]
|
|
450
|
+
"""
|
|
451
|
+
def __init__(self, intervals: list[Interval]) -> None:
|
|
452
|
+
self.intervals = intervals
|
|
453
|
+
|
|
454
|
+
def __contains__(self, iterable) -> bool:
|
|
455
|
+
return all(entry in interval for entry, interval in zip(iterable, self.intervals))
|
|
456
|
+
|
|
457
|
+
def __len__(self) -> int:
|
|
458
|
+
return len(self.intervals)
|
|
459
|
+
|
|
460
|
+
def __getitem__(self, i: int) -> Interval:
|
|
461
|
+
return self.intervals[i]
|
|
462
|
+
|
|
463
|
+
def _repr_(self) -> str:
|
|
464
|
+
if len(self) == 0:
|
|
465
|
+
return "()"
|
|
466
|
+
return " x ".join(str(interval) for interval in self.intervals)
|
|
467
|
+
|
|
468
|
+
def _latex_(self) -> str:
|
|
469
|
+
r"""
|
|
470
|
+
Return a LaTeX representation of the intervals.
|
|
471
|
+
|
|
472
|
+
EXAMPLES::
|
|
473
|
+
|
|
474
|
+
sage: from certlin import *
|
|
475
|
+
sage: Intervals([Interval(0, 1), Interval(-5, 2, False, False)])._latex_()
|
|
476
|
+
'[0, 1] \\times (-5, 2)'
|
|
477
|
+
sage: Intervals([Interval(-oo, 1), Interval(2, 2, False, False)])._latex_()
|
|
478
|
+
'(-\\infty, 1] \\times \\emptyset'
|
|
479
|
+
"""
|
|
480
|
+
return " \\times ".join(interval._latex_() for interval in self.intervals)
|
|
481
|
+
|
|
482
|
+
def __eq__(self, other) -> bool:
|
|
483
|
+
return self.intervals == other.intervals
|
|
484
|
+
|
|
485
|
+
def __hash__(self) -> int:
|
|
486
|
+
return hash(tuple(self.intervals))
|
|
487
|
+
|
|
488
|
+
def is_empty(self) -> bool:
|
|
489
|
+
r"""Return whether the intervals are empty."""
|
|
490
|
+
if len(self) == 0:
|
|
491
|
+
return True
|
|
492
|
+
return any(interval.is_empty() for interval in self.intervals)
|
|
493
|
+
|
|
494
|
+
def is_open(self) -> bool:
|
|
495
|
+
r"""Return whether all intervals are open."""
|
|
496
|
+
return all(interval.is_open() for interval in self.intervals)
|
|
497
|
+
|
|
498
|
+
def is_closed(self) -> bool:
|
|
499
|
+
r"""Return whether all intervals are closed."""
|
|
500
|
+
return all(interval.is_closed() for interval in self.intervals)
|
|
501
|
+
|
|
502
|
+
def is_unbounded(self) -> bool:
|
|
503
|
+
r"""Return whether any interval is unbounded."""
|
|
504
|
+
return any(interval.is_unbounded() for interval in self.intervals)
|
|
505
|
+
|
|
506
|
+
def is_bounded(self) -> bool:
|
|
507
|
+
r"""Return whether all intervals are bounded."""
|
|
508
|
+
return not self.is_unbounded()
|
|
509
|
+
|
|
510
|
+
def is_pointed(self) -> bool:
|
|
511
|
+
r"""Return whether all intervals are pointed."""
|
|
512
|
+
return all(interval.is_pointed() for interval in self.intervals)
|
|
513
|
+
|
|
514
|
+
def __bool__(self) -> bool:
|
|
515
|
+
return not self.is_empty()
|
|
516
|
+
|
|
517
|
+
def an_element(self):
|
|
518
|
+
r"""Return an element from each interval."""
|
|
519
|
+
return [interval.an_element() for interval in self.intervals]
|
|
520
|
+
|
|
521
|
+
def simplest_element(self):
|
|
522
|
+
r"""Return the simplest element from each interval."""
|
|
523
|
+
return [interval.simplest_element() for interval in self.intervals]
|
|
524
|
+
|
|
525
|
+
def __iter__(self):
|
|
526
|
+
return iter(self.intervals)
|
|
527
|
+
|
|
528
|
+
@classmethod
|
|
529
|
+
def random(cls, length: int, ring=QQ) -> Intervals:
|
|
530
|
+
r"""
|
|
531
|
+
Generate a random list of intervals.
|
|
532
|
+
|
|
533
|
+
EXAMPLES::
|
|
534
|
+
|
|
535
|
+
sage: from certlin import *
|
|
536
|
+
sage: Intervals.random(3) # random
|
|
537
|
+
[0, +oo) x (-5, 2) x (0, 1]
|
|
538
|
+
"""
|
|
539
|
+
return cls([Interval.random(ring) for _ in range(length)])
|
|
540
|
+
|
|
541
|
+
@classmethod
|
|
542
|
+
def from_bounds(cls, lower_bounds: list, upper_bounds: list, lower_bounds_closed: bool = True, upper_bounds_closed: bool = True) -> Intervals:
|
|
543
|
+
r"""
|
|
544
|
+
Return intervals that are determined by bounds.
|
|
545
|
+
|
|
546
|
+
EXAMPLES::
|
|
547
|
+
|
|
548
|
+
sage: from certlin import *
|
|
549
|
+
sage: Intervals.from_bounds([0, -5, 0], [1, 2, +oo])
|
|
550
|
+
[0, 1] x [-5, 2] x [0, +oo)
|
|
551
|
+
sage: Intervals.from_bounds([0, -5, 0], [1, 2, +oo], False, False)
|
|
552
|
+
(0, 1) x (-5, 2) x (0, +oo)
|
|
553
|
+
sage: Intervals.from_bounds([0, -5, 0], [1, 2, +oo], True, False)
|
|
554
|
+
[0, 1) x [-5, 2) x [0, +oo)
|
|
555
|
+
sage: Intervals.from_bounds([0, -5, 0], [1, 2, +oo], [True, False, True], [True, True, False])
|
|
556
|
+
[0, 1] x (-5, 2] x [0, +oo)
|
|
557
|
+
"""
|
|
558
|
+
length = len(lower_bounds)
|
|
559
|
+
if lower_bounds_closed is True:
|
|
560
|
+
lower_bounds_closed = [True] * length
|
|
561
|
+
elif lower_bounds_closed is False:
|
|
562
|
+
lower_bounds_closed = [False] * length
|
|
563
|
+
|
|
564
|
+
if upper_bounds_closed is True:
|
|
565
|
+
upper_bounds_closed = [True] * length
|
|
566
|
+
elif upper_bounds_closed is False:
|
|
567
|
+
upper_bounds_closed = [False] * length
|
|
568
|
+
|
|
569
|
+
return cls([
|
|
570
|
+
Interval(*bounds)
|
|
571
|
+
for bounds in zip(
|
|
572
|
+
lower_bounds, upper_bounds, lower_bounds_closed, upper_bounds_closed
|
|
573
|
+
)
|
|
574
|
+
])
|
|
575
|
+
|
|
576
|
+
@classmethod
|
|
577
|
+
def from_sign_vector(cls, sv: SignVector) -> Intervals:
|
|
578
|
+
r"""
|
|
579
|
+
Return intervals that are determined by a sign vector.
|
|
580
|
+
|
|
581
|
+
EXAMPLES::
|
|
582
|
+
|
|
583
|
+
sage: from certlin import *
|
|
584
|
+
sage: from sign_vectors import *
|
|
585
|
+
sage: sv = sign_vector("+0-")
|
|
586
|
+
sage: Intervals.from_sign_vector(sv)
|
|
587
|
+
(0, +oo) x {0} x (-oo, 0)
|
|
588
|
+
"""
|
|
589
|
+
return cls([
|
|
590
|
+
Interval(
|
|
591
|
+
0 if element > 0 else (minus_infinity if element < 0 else 0),
|
|
592
|
+
Infinity if element > 0 else (0 if element < 0 else 0),
|
|
593
|
+
element == 0,
|
|
594
|
+
element == 0
|
|
595
|
+
)
|
|
596
|
+
for element in sv
|
|
597
|
+
])
|
|
598
|
+
|
|
599
|
+
def sign_vectors(self, generator: bool = False) -> list[SignVector] | Iterator[SignVector]:
|
|
600
|
+
r"""
|
|
601
|
+
Compute all sign vectors that correspond to a vector with components in given intervals.
|
|
602
|
+
|
|
603
|
+
INPUT:
|
|
604
|
+
|
|
605
|
+
- ``intervals`` -- a list of intervals
|
|
606
|
+
- ``generator`` -- a boolean (default: ``False``)
|
|
607
|
+
|
|
608
|
+
EXAMPLES::
|
|
609
|
+
|
|
610
|
+
sage: from certlin import *
|
|
611
|
+
sage: intervals = Intervals.from_bounds([-1, 1], [0, 1])
|
|
612
|
+
sage: intervals.sign_vectors()
|
|
613
|
+
[(0+), (-+)]
|
|
614
|
+
sage: intervals = Intervals.from_bounds([-1, -2], [0, 1])
|
|
615
|
+
sage: intervals.sign_vectors()
|
|
616
|
+
[(00), (0+), (0-), (-0), (-+), (--)]
|
|
617
|
+
sage: intervals = Intervals.from_bounds([-1, -1, 0], [0, 5, 0])
|
|
618
|
+
sage: intervals.sign_vectors()
|
|
619
|
+
[(000), (0+0), (0-0), (-00), (-+0), (--0)]
|
|
620
|
+
sage: intervals = Intervals.from_bounds([-1, -1, -1], [0, 1, 0], False, False)
|
|
621
|
+
sage: intervals.sign_vectors()
|
|
622
|
+
[(-0-), (-+-), (---)]
|
|
623
|
+
|
|
624
|
+
TESTS::
|
|
625
|
+
|
|
626
|
+
sage: intervals = Intervals.from_bounds([-1, 0], [1, 0], False, False)
|
|
627
|
+
sage: intervals.sign_vectors()
|
|
628
|
+
[]
|
|
629
|
+
sage: intervals = Intervals.from_bounds([], [])
|
|
630
|
+
sage: intervals.sign_vectors()
|
|
631
|
+
[]
|
|
632
|
+
"""
|
|
633
|
+
list_of_signs = []
|
|
634
|
+
if self.is_empty():
|
|
635
|
+
if generator:
|
|
636
|
+
def empty():
|
|
637
|
+
yield from ()
|
|
638
|
+
return empty()
|
|
639
|
+
return []
|
|
640
|
+
for interval in self:
|
|
641
|
+
available_signs = []
|
|
642
|
+
if 0 in interval:
|
|
643
|
+
available_signs.append(0)
|
|
644
|
+
if interval.supremum() > 0:
|
|
645
|
+
available_signs.append(1)
|
|
646
|
+
if interval.infimum() < 0:
|
|
647
|
+
available_signs.append(-1)
|
|
648
|
+
list_of_signs.append(available_signs)
|
|
649
|
+
|
|
650
|
+
if generator:
|
|
651
|
+
return (
|
|
652
|
+
sign_vector(signs) for signs in cartesian_product_iterator(list_of_signs)
|
|
653
|
+
)
|
|
654
|
+
return [sign_vector(signs) for signs in cartesian_product_iterator(list_of_signs)]
|