1+ """Test for python-flint's `arb` type."""
2+
3+ import math
4+
5+ from flint import arb , ctx
6+
7+ def assert_almost_equal (x , y , places = 7 ):
8+ """Helper method for approximate comparisons."""
9+ assert round (x - y , ndigits = places ) == 0
10+
11+ def test_from_int ():
12+ """Tests instantiating `arb`s from ints."""
13+ for val in [
14+ - 42 * 10 ** 9 ,
15+ - 42 * 10 ** 7 ,
16+ - 42 ,
17+ 0 ,
18+ 42 ,
19+ 42 * 10 ** 7 ,
20+ 42 * 10 ** 9 ,
21+ 42 * 10 ** 11 ,
22+ ]:
23+ x = arb (val )
24+ man , exp = x .man_exp ()
25+ assert (man * 2 ** exp ) == val
26+
27+ def test_from_float ():
28+ """Tests instantiating `arb`s from floats."""
29+ for val in [0.0 , 1.1 , - 1.1 , 9.9 * 0.123 , 99.12 ]:
30+ x = arb (val )
31+ man , exp = x .man_exp ()
32+ assert (int (man ) * 2 ** int (exp )) == val
33+
34+ def test_from_float_inf ():
35+ """Tests `arb` works with +/- inf."""
36+ posinf = arb (float ("inf" ))
37+ neginf = arb (float ("-inf" ))
38+
39+ assert not posinf .is_finite ()
40+ assert not neginf .is_finite ()
41+ assert float (posinf ) == float ("inf" )
42+ assert float (neginf ) == float ("-inf" )
43+
44+ def test_from_man_exp ():
45+ """Tests instantiating `arb`s with mantissa and exponent."""
46+ for man , exp in [(2 , 30 ), (4 , 300 ), (5 * 10 ** 2 , 7 ** 8 )]:
47+ x = arb (mid = (man , exp ))
48+ m , e = x .man_exp ()
49+ assert (m * 2 ** e ) == (man * 2 ** exp )
50+
51+ def test_from_midpoint_radius ():
52+ """Tests instantiating `arb`s with midpoint and radius."""
53+ for mid , rad in [(10 , 1 ), (10000 , 5 ), (10 , 1 ), (10 , 1 )]:
54+ mid_arb = arb (mid )
55+ rad_arb = arb (rad )
56+ x = arb (mid_arb , rad_arb )
57+ assert x .mid () == mid_arb
58+ actual_radius = float (x .rad ())
59+ assert_almost_equal (actual_radius , rad )
60+
61+ def test_is_exact ():
62+ """Tests `arb.is_exact`."""
63+ for arb_val , exact in [
64+ (arb (10 ), True ),
65+ (arb (0.01 ), True ),
66+ (arb (- float ("inf" )), True ),
67+ (arb (1 , 0 ), True ),
68+ (arb (1 , 1 ), False ),
69+ ]:
70+ assert arb_val .is_exact () == exact
71+
72+ def test_is_finite ():
73+ """Tests `arb.is_finite`."""
74+ assert not (arb (- float ("inf" )).is_finite ())
75+ assert not (arb (float ("inf" )).is_finite ())
76+ assert (arb (10 ).is_finite ())
77+
78+ def test_is_nan ():
79+ """Tests `arb.is_nan`."""
80+ assert (arb (float ("nan" )).is_nan ())
81+ assert not (arb (0.0 ).is_nan ())
82+
83+ def test_lower ():
84+ """Tests `arb.lower`."""
85+ with ctx .workprec (100 ):
86+ arb_val = arb (1 , 0.5 )
87+ assert_almost_equal (float (arb_val .lower ()), 0.5 )
88+
89+ def test_upper ():
90+ """Tests `arb.upper`."""
91+ with ctx .workprec (100 ):
92+ arb_val = arb (1 , 0.5 )
93+ assert_almost_equal (float (arb_val .upper ()), 1.5 )
94+
95+ def test_contains ():
96+ """`y.__contains__(x)` returns True iff every number in `x` is also in `y`."""
97+ for x , y , expected in [
98+ (
99+ arb (mid = 9 , rad = 1 ),
100+ arb (mid = 10 , rad = 2 ),
101+ True ,
102+ ),
103+ (
104+ arb (mid = 10 , rad = 2 ),
105+ arb (mid = 9 , rad = 1 ),
106+ False ,
107+ ),
108+ (arb (10 ), arb (mid = 9 , rad = 1 ), True ),
109+ (arb (10.1 ), arb (mid = 9 , rad = 1 ), False ),
110+ ]:
111+ assert (x in y ) == expected
112+
113+ # TODO: Re-enable this if we ever add the ability to hash arbs.
114+ # def test_hash():
115+ # """`x` and `y` hash to the same value if they have the same midpoint and radius.
116+
117+ # Args:
118+ # x: An arb.
119+ # y: An arb.
120+ # expected: Whether `x` and `y` should hash to the same value.
121+ # """
122+ # def arb_pi(prec):
123+ # """Helper to calculate arb to a given precision."""
124+ # with ctx.workprec(prec):
125+ # return arb.pi()
126+ # for x, y, expected in [
127+ # (arb(10), arb(10), True),
128+ # (arb(10), arb(11), False),
129+ # (arb(10.0), arb(10), True),
130+ # (
131+ # arb(mid=10, rad=2),
132+ # arb(mid=10, rad=2),
133+ # True,
134+ # ),
135+ # (
136+ # arb(mid=10, rad=2),
137+ # arb(mid=10, rad=3),
138+ # False,
139+ # ),
140+ # (arb_pi(100), arb_pi(100), True),
141+ # (arb_pi(100), arb_pi(1000), False),
142+ # ]:
143+ # assert (hash(x) == hash(y)) == expected
144+
145+
146+
147+ # Tests for arithmetic functions in `flint.arb`.
148+
149+ # NOTE: Correctness of an arb function `F` is specified as follows:
150+
151+ # If `f` is the corresponding real-valued arithmetic function, `F` is correct
152+ # only if, for any Arb X and any real number x in the interval X,
153+ # `f(x)` is in `F(X)`.
154+
155+ # These tests assume arb.__contains__ is correct.
156+
157+ def test_arb_sub ():
158+ """`arb.__sub__` works as expected."""
159+ arb1 = arb (2 , 0.5 )
160+ arb2 = arb (1 , 1 )
161+ with ctx .workprec (100 ):
162+ actual = arb1 - arb2
163+ # Smallest value in diff => 1.5 - 2 = -0.5
164+ # Largest value in diff => 2.5 - 0 = 2.5
165+ true_interval = arb (1 , 1.5 ) # [-0.5, 2.5]
166+ assert true_interval in actual
167+
168+ def test_arb_add ():
169+ """`arb.__add__` works as expected."""
170+ arb1 = arb (2 , 1 )
171+ arb2 = arb (1 , 1 )
172+ with ctx .workprec (100 ):
173+ actual = arb1 + arb2
174+ true_interval = arb (3 , 2 ) # [1, 5]
175+ assert true_interval in actual
176+
177+ def test_arb_mul ():
178+ """`arb.__mul__` works as expected."""
179+ arb1 = arb (2 , 1 )
180+ arb2 = arb (1 , 1 )
181+ with ctx .workprec (100 ):
182+ actual = arb1 * arb2
183+ true_interval = arb (3 , 3 ) # [0, 6]
184+ assert true_interval in actual
185+
186+ def test_arb_div ():
187+ """`arb.__div__` works as expected."""
188+ arb1 = arb (4 , 1 )
189+ arb2 = arb (2 , 1 )
190+ with ctx .workprec (100 ):
191+ actual = arb1 / arb2
192+ true_interval = arb (4 , 1 ) # [3, 5]
193+ assert true_interval in actual
194+
195+ def test_arb_log ():
196+ """`arb.log` works as expected."""
197+ midpoint = (1 + math .exp (10 )) / 2
198+ arb_val = arb (midpoint , midpoint - 1 ) # [1, exp(10)]
199+ with ctx .workprec (100 ):
200+ actual = arb_val .log ()
201+ true_interval = arb (5 , 5 ) # [0,10]
202+ assert true_interval in actual
203+
204+ def test_arb_exp ():
205+ """`arb.exp` works as expected."""
206+ midpoint = math .log (9 ) / 2
207+ arb_val = arb (midpoint , midpoint ) # [0, log(9)]
208+ with ctx .workprec (100 ):
209+ actual = arb_val .exp ()
210+ true_interval = arb (5 , 4 ) # [1,9]
211+ assert true_interval in actual
212+
213+ def test_arb_max ():
214+ """`arb.max` works as expected."""
215+ arb1 = arb (1.5 , 0.5 ) # [1, 2]
216+ arb2 = arb (1 , 2 ) # [-1, 3]
217+ with ctx .workprec (100 ):
218+ actual = arb1 .max (arb2 )
219+ true_interval = arb (2 , 1 ) # [1, 3]
220+ assert true_interval in actual
221+
222+ def test_arb_min ():
223+ """`arb.min` works as expected."""
224+ arb1 = arb (1.5 , 0.5 ) # [1, 2]
225+ arb2 = arb (1 , 2 ) # [-1, 3]
226+ with ctx .workprec (100 ):
227+ actual = arb1 .min (arb2 )
228+ true_interval = arb (0.5 , 1.5 ) # [-1, 2]
229+ assert true_interval in actual
230+
231+ def test_arb_abs ():
232+ """`arb.__abs__` works as expected."""
233+ arb_val = arb (1 , 2 ) # [-1,3]
234+ actual = abs (arb_val )
235+ true_interval = arb (1.5 , 1.5 )
236+ assert true_interval in actual
237+
238+ def test_arb_neg ():
239+ """`arb.neg` works as expected."""
240+ arb_val = arb (1 , 2 ) # [-1,3]
241+ actual = arb_val .neg (exact = True )
242+ true_interval = arb (- 2 , 1 ) # [-3,1]
243+ assert true_interval in actual
244+
245+ def test_arb_neg_dunder ():
246+ """`arb.__neg__` works as expected."""
247+ arb_val = arb (1 , 2 ) # [-1,3]
248+ actual = - arb_val
249+ true_interval = arb (- 2 , 1 ) # [-3,1]
250+ assert true_interval in actual
251+
252+ def test_arb_sgn ():
253+ """`arb.sgn` works as expected."""
254+ arb1 = arb (1 , 0.5 ) # [0.5,1.5]
255+ arb2 = arb (- 1 , 0.5 ) # [-1.5,-0.5]
256+ arb3 = arb (1 , 2 ) # [-1,3]
257+ assert_almost_equal (float (arb1 .sgn ()), 1 )
258+ assert_almost_equal (float (arb2 .sgn ()), - 1 )
259+ # arb3 contains both positive and negative numbers
260+ # So, arb_sgn returns [0, 1]
261+ assert_almost_equal (float (arb3 .sgn ().mid ()), 0 )
262+ assert_almost_equal (float (arb3 .sgn ().rad ()), 1 )
263+
264+ def test_arb_erfinv ():
265+ """`arb.erfinv` works as expected."""
266+ midpoint = (math .erf (1 / 8 ) + math .erf (1 / 16 )) / 2
267+ radius = midpoint - math .erf (1 / 16 )
268+ arb_val = arb (midpoint , radius )
269+ with ctx .workprec (100 ):
270+ actual = arb_val .erfinv ()
271+ true_interval = arb (3 / 32 , 1 / 32 ) # [1/16, 1/8]
272+ assert true_interval in actual
273+
274+ def test_arb_erf ():
275+ """`arb.erf` works as expected."""
276+ arb_val = arb (2 , 1 )
277+ with ctx .workprec (100 ):
278+ actual = arb_val .erf ()
279+ true_interval = arb (
280+ (math .erf (1 ) + math .erf (3 )) / 2 ,
281+ (math .erf (1 ) + math .erf (3 )) / 2 - math .erf (1 )
282+ )
283+ assert true_interval in actual
284+
285+ def test_arb_erfc ():
286+ """`arb.erfc` works as expected."""
287+ arb_val = arb (2 , 1 )
288+ with ctx .workprec (100 ):
289+ actual = arb_val .erfc ()
290+ true_interval = arb (
291+ (math .erfc (1 ) + math .erfc (3 )) / 2 ,
292+ (math .erfc (1 ) + math .erfc (3 )) / 2 - math .erfc (3 )
293+ )
294+ assert true_interval in actual
295+
296+ def test_arb_const_pi ():
297+ """`arb.pi` works as expected."""
298+ with ctx .workprec (100 ):
299+ actual = arb .pi ()
300+ interval_around_pi = arb (math .pi , 1e-10 )
301+ assert actual in interval_around_pi
302+
303+ def test_arb_union ():
304+ """`arb.union` works as expected."""
305+ arb1 = arb (1 , 0.5 ) # [0.5,1.5]
306+ arb2 = arb (3 , 0.5 ) # [2.5,3.5]
307+ with ctx .workprec (100 ):
308+ actual = arb1 .union (arb2 )
309+ true_interval = arb (2 , 1.5 ) # [0.5, 3.5]
310+ assert true_interval in actual
311+
312+ def test_arb_sum ():
313+ """`arb.__sum__` works as expected."""
314+ arb1 = arb (1 , 0.5 ) # [0.5,1.5]
315+ arb2 = arb (2 , 0.5 ) # [1.5,2.5]
316+ arb3 = arb (3 , 0.5 ) # [2.5,3.5]
317+ with ctx .workprec (100 ):
318+ actual = arb1 + arb2 + arb3
319+ true_interval = arb (6 , 1.5 ) # [4.5, 7.5]
320+ assert true_interval in actual
321+
322+ def test_no_tests_missing ():
323+ """Make sure all arb tests are included in all_tests."""
324+ test_funcs = {f for name , f in globals ().items () if name .startswith ("test_" )}
325+ untested = test_funcs - set (all_tests )
326+ assert not untested , f"Untested functions: { untested } "
327+
328+ all_tests = [
329+ test_no_tests_missing ,
330+ test_from_int ,
331+ test_from_float ,
332+ test_from_float_inf ,
333+ test_from_man_exp ,
334+ test_from_midpoint_radius ,
335+ test_is_exact ,
336+ test_is_finite ,
337+ test_is_nan ,
338+ test_lower ,
339+ test_upper ,
340+ test_contains ,
341+ # TODO: Re-enable this if we ever add the ability to hash arbs.
342+ # test_hash,
343+ test_arb_sub ,
344+ test_arb_add ,
345+ test_arb_mul ,
346+ test_arb_div ,
347+ test_arb_log ,
348+ test_arb_exp ,
349+ test_arb_max ,
350+ test_arb_min ,
351+ test_arb_abs ,
352+ test_arb_neg ,
353+ test_arb_neg_dunder ,
354+ test_arb_sgn ,
355+ test_arb_erfinv ,
356+ test_arb_erf ,
357+ test_arb_erfc ,
358+ test_arb_const_pi ,
359+ test_arb_union ,
360+ test_arb_sum ,
361+ ]
0 commit comments