-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtest_numutil.py
More file actions
111 lines (93 loc) · 5.49 KB
/
test_numutil.py
File metadata and controls
111 lines (93 loc) · 5.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# -*- coding: utf-8 -*-
from ..syntax import macros, test, test_raises, warn, the # noqa: F401
from ..test.fixtures import session, testset
from itertools import count, takewhile
from math import cos, sqrt
import sys
from ..numutil import (almosteq, fixpoint, ulp,
partition_int, partition_int_triangular, partition_int_custom)
from ..it import rev
def runtests():
with testset("ulp (unit in the last place; float utility)"):
test[ulp(1.0) == sys.float_info.epsilon]
# test also at some base-2 exponent switch points
test[ulp(2.0) == 2 * sys.float_info.epsilon]
test[ulp(0.5) == 0.5 * sys.float_info.epsilon]
with testset("almosteq"):
# For anything but floating-point inputs, it's exact equality.
test[almosteq("abc", "abc")]
test[not almosteq("ab", "abc")]
test[almosteq(1.0, 1.0 + ulp(1.0))]
# TODO: counterintuitively, need a large tolerance here, because when one operand is zero,
# TODO: the final tolerance is actually tol*min_normal.
min_normal = sys.float_info.min
test[almosteq(min_normal / 2, 0, tol=1.0)]
too_large = 2**int(1e6)
test_raises[OverflowError, float(too_large), "UPDATE THIS, need a float overflow here."]
test[almosteq(too_large, too_large + 1)] # works, because 1/too_large is very small.
try:
from mpmath import mpf
except ImportError: # pragma: no cover
warn["mpmath not installed in this Python, skipping arbitrary precision input tests."]
else:
test[almosteq(mpf(1.0), mpf(1.0 + ulp(1.0)))]
test[almosteq(1.0, mpf(1.0 + ulp(1.0)))]
test[almosteq(mpf(1.0), 1.0 + ulp(1.0))]
# Arithmetic fixed points.
with testset("fixpoint (arithmetic fixed points)"):
c = fixpoint(cos, x0=1)
test[the[c] == the[cos(c)]] # 0.7390851332151607
# Actually "Newton's" algorithm for the square root was already known to the
# ancient Babylonians, ca. 2000 BCE. (Carl Boyer: History of mathematics)
# Concerning naming, see also https://en.wikipedia.org/wiki/Stigler's_law_of_eponymy
def sqrt_newton(n):
def sqrt_iter(x): # has an attractive fixed point at sqrt(n)
return (x + n / x) / 2
return fixpoint(sqrt_iter, x0=n / 2)
# different algorithm, so not necessarily equal down to the last bit
# (caused by the fixpoint update becoming smaller than the ulp, so it
# stops there, even if the limit is still one ulp away).
test[abs(the[sqrt_newton(2)] - the[sqrt(2)]) <= the[ulp(1.414)]]
# partition_int: split a small positive integer, in all possible ways, into smaller integers that sum to it
with testset("partition_int"):
test[tuple(partition_int(4)) == ((4,), (3, 1), (2, 2), (2, 1, 1), (1, 3), (1, 2, 1), (1, 1, 2), (1, 1, 1, 1))]
test[tuple(partition_int(5, lower=2)) == ((5,), (3, 2), (2, 3))]
test[tuple(partition_int(5, lower=2, upper=3)) == ((3, 2), (2, 3))]
test[tuple(partition_int(10, lower=3, upper=5)) == ((5, 5), (4, 3, 3), (3, 4, 3), (3, 3, 4))]
test[all(sum(terms) == 10 for terms in partition_int(10))]
test[all(sum(terms) == 10 for terms in partition_int(10, lower=3))]
test[all(sum(terms) == 10 for terms in partition_int(10, lower=3, upper=5))]
test_raises[TypeError, partition_int("not a number")]
test_raises[TypeError, partition_int(4, lower="not a number")]
test_raises[TypeError, partition_int(4, upper="not a number")]
test_raises[ValueError, partition_int(-3)]
test_raises[ValueError, partition_int(4, lower=-1)]
test_raises[ValueError, partition_int(4, lower=5)]
test_raises[ValueError, partition_int(4, upper=-1)]
test_raises[ValueError, partition_int(4, upper=5)]
test_raises[ValueError, partition_int(4, lower=3, upper=2)]
# partition_int_triangular: like partition_int, but in the output, allow triangular numbers only.
# Triangular numbers are 1, 3, 6, 10, ...
with testset("partition_int_triangular"):
test[frozenset(tuple(sorted(c)) for c in partition_int_triangular(78, lower=10)) ==
frozenset({(10, 10, 10, 10, 10, 28),
(10, 10, 15, 15, 28),
(15, 21, 21, 21),
(21, 21, 36),
(78,)})]
# partition_int_custom: like partition_int, but lets you specify allowed components manually.
# Can be used to build other functions like `partition_int` and `partition_int_triangular`.
with testset("partition_int_custom"):
test[tuple(partition_int_custom(4, [1])) == ((1, 1, 1, 1),)]
test[tuple(partition_int_custom(4, [1, 3])) == ((1, 1, 1, 1), (1, 3), (3, 1))]
evens_upto_n = lambda n: takewhile(lambda m: m <= n, count(start=2, step=2))
test[tuple(partition_int_custom(4, rev(evens_upto_n(4)))) == ((4,), (2, 2))]
test[tuple(partition_int_custom(6, rev(evens_upto_n(6)))) == ((6,), (4, 2), (2, 4), (2, 2, 2))]
test_raises[TypeError, partition_int_custom("not a number", evens_upto_n("blah"))]
test_raises[TypeError, tuple(partition_int_custom(4, [2.0]))]
test_raises[ValueError, partition_int_custom(-3, evens_upto_n(-3))]
test_raises[ValueError, tuple(partition_int_custom(4, [-1]))]
test_raises[ValueError, tuple(partition_int_custom(4, [1, -1]))]
if __name__ == '__main__': # pragma: no cover
with session(__file__):
runtests()