Skip to content

Commit 626f405

Browse files
committed
add cond
1 parent d0e4c92 commit 626f405

File tree

8 files changed

+94
-68
lines changed

8 files changed

+94
-68
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ jobs:
3434
- name: Test with tox
3535
run: |
3636
pip install tox
37-
tox
37+
tox -- --cov accupy --cov-report xml --cov-report term
3838
- uses: codecov/codecov-action@v1
3939
if: ${{ matrix.python-version == '3.9' }}

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ p, exact, cond = accupy.generate_ill_conditioned_sum(100, 1.0e20)
3636
which, given a length and a target condition number, will produce an array of
3737
floating point numbers that is hard to sum up.
3838

39+
Given one or two vectors, accupy can compute the condition of the sum or dot product via
40+
41+
```python
42+
accupy.cond(x)
43+
accupy.cond(x, y)
44+
```
45+
3946
accupy has the following methods for summation:
4047

4148
- `accupy.kahan_sum(p)`: [Kahan

accupy/__init__.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
from .dot import fdot, kdot
2-
from .ill_cond import generate_ill_conditioned_dot_product, generate_ill_conditioned_sum
2+
from .ill_cond import (
3+
cond,
4+
generate_ill_conditioned_dot_product,
5+
generate_ill_conditioned_sum,
6+
)
37
from .sums import decker_sum, distill, fsum, kahan_sum, knuth_sum, ksum
48

59
__all__ = [
10+
"cond",
611
"kdot",
712
"fdot",
813
"generate_ill_conditioned_sum",

accupy/ill_cond.py

+24-23
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,46 @@
11
import math
2-
from typing import Tuple
2+
from typing import Optional, Tuple
33

44
import numpy as np
55
import pyfma
6-
from mpmath import mp
6+
from numpy.typing import ArrayLike
77

8+
from .dot import fdot, fsum
89

9-
def generate_ill_conditioned_sum(
10-
n: int, c: float, dps: int = 100
11-
) -> Tuple[np.ndarray, float, float]:
10+
11+
def cond(x: ArrayLike, y: Optional[ArrayLike] = None) -> float:
12+
"""Compute the condition number of a sum (if only x is given) or a dot-product (if
13+
both x and y are given).
14+
"""
15+
if y is None:
16+
return fsum(np.abs(x)) / np.abs(fsum(x))
17+
18+
return 2 * fdot(np.abs(x), np.abs(y)) / abs(fdot(x, y))
19+
20+
21+
def generate_ill_conditioned_sum(n: int, c: float) -> Tuple[np.ndarray, float, float]:
1222
# From <https://doi.org/10.1137/030601818>:
1323
# Ill-conditioned sums of length 2n are generated from dot products of
1424
# length n using Algorithm 3.3 (TwoProduct) and randomly permuting the
1525
# summands.
16-
x, y, _, C = generate_ill_conditioned_dot_product(n, c, dps)
26+
x, y, _, C = generate_ill_conditioned_dot_product(n, c)
1727

1828
prod = x * y
1929
err = pyfma.fma(x, y, -prod)
2030
res = np.array([prod, err])
2131

2232
out = np.random.permutation(res.flatten())
2333

24-
def sum_exact(p):
25-
mp.dps = dps
26-
return mp.fsum(p)
34+
exact = fsum(out)
2735

28-
exact = sum_exact(out)
29-
# cond = sum_exact(np.abs(out)) / abs(exact)
30-
cond = C / 2
36+
# condition = fsum(np.abs(out)) / abs(exact)
37+
condition = C / 2
3138

32-
return out, exact, cond
39+
return out, exact, condition
3340

3441

3542
def generate_ill_conditioned_dot_product(
36-
n: int, c: float, dps: int = 100
43+
n: int, c: float
3744
) -> Tuple[np.ndarray, np.ndarray, float, float]:
3845
"""n ... length of vector
3946
c ... target condition number
@@ -60,12 +67,6 @@ def generate_ill_conditioned_dot_product(
6067
x[:n2] = (2 * rx - 1) * 2 ** e
6168
y[:n2] = (2 * ry - 1) * 2 ** e
6269

63-
def dot_exact(x, y):
64-
mp.dps = dps
65-
# convert to list first, see
66-
# <https://github.com/fredrik-johansson/mpmath/pull/385>
67-
return mp.fdot(x.tolist(), y.tolist())
68-
6970
# for i=n2+1:n and v=1:i,
7071
# generate x_i, y_i such that (*) x(v)’*y(v) ~ 2^e(i-n2)
7172
# generate exponents for second half
@@ -76,13 +77,13 @@ def dot_exact(x, y):
7677
x[i] = (2 * rx[i - n2] - 1) * 2 ** e[i - n2]
7778
# y_i according to (*)
7879
y[i] = (
79-
(2 * ry[i - n2] - 1) * 2 ** e[i - n2] - dot_exact(x[: i + 1], y[: i + 1])
80+
(2 * ry[i - n2] - 1) * 2 ** e[i - n2] - fdot(x[: i + 1], y[: i + 1])
8081
) / x[i]
8182

8283
x, y = np.random.permutation((x, y))
8384
# the true dot product rounded to nearest floating point
84-
d = dot_exact(x, y)
85+
d = fdot(x, y)
8586
# the actual condition number
86-
C = 2 * dot_exact(abs(x), abs(y)) / abs(d)
87+
C = 2 * fdot(abs(x), abs(y)) / abs(d)
8788

8889
return x, y, d, C

justfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ upload: clean
1313
@if [ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]; then exit 1; fi
1414
# https://stackoverflow.com/a/58756491/353337
1515
python3 -m build --sdist --wheel .
16-
twine upload dist/*
16+
twine upload dist/*.tar.gz
1717

1818
publish: tag upload
1919

tests/test_dot.py

+24-17
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import dufte
22
import matplotlib.pyplot as plt
3-
import numpy
3+
import numpy as np
44
import perfplot
55
import pytest
66

77
import accupy
88

99

10+
def test_cond():
11+
cond = accupy.cond([np.pi, np.e], [23225 / 8544, -355 / 113])
12+
print(cond)
13+
ref = 4.852507317687677e7
14+
assert abs(cond - ref) < 1.0e-13 * abs(ref)
15+
16+
1017
@pytest.mark.parametrize("cond", [1.0, 1.0e15])
1118
def test_kdot2(cond):
1219
x, y, ref, _ = accupy.generate_ill_conditioned_dot_product(100, cond)
@@ -32,22 +39,22 @@ def test_accuracy_comparison_illcond(target_cond=None):
3239
target_cond = [10 ** k for k in range(2)]
3340

3441
kernels = [
35-
numpy.dot,
42+
np.dot,
3643
lambda x, y: accupy.kdot(x, y, K=2),
3744
lambda x, y: accupy.kdot(x, y, K=3),
3845
accupy.fdot,
3946
]
40-
labels = ["numpy.dot", "accupy.kdot[2]", "accupy.kdot[3]", "accupy.fdot"]
41-
data = numpy.empty((len(target_cond), len(kernels)))
42-
condition_numbers = numpy.empty(len(target_cond))
43-
numpy.random.seed(0)
47+
labels = ["np.dot", "accupy.kdot[2]", "accupy.kdot[3]", "accupy.fdot"]
48+
data = np.empty((len(target_cond), len(kernels)))
49+
condition_numbers = np.empty(len(target_cond))
50+
np.random.seed(0)
4451
for k, tc in enumerate(target_cond):
4552
x, y, ref, C = accupy.generate_ill_conditioned_dot_product(1000, tc)
4653
condition_numbers[k] = C
4754
data[k] = [abs(kernel(x, y) - ref) / abs(ref) for kernel in kernels]
4855

4956
# sort
50-
s = numpy.argsort(condition_numbers)
57+
s = np.argsort(condition_numbers)
5158
condition_numbers = condition_numbers[s]
5259
data = data[s]
5360

@@ -65,16 +72,16 @@ def test_speed_comparison1(n_range=None):
6572
if n_range is None:
6673
n_range = [2 ** k for k in range(2)]
6774

68-
numpy.random.seed(0)
75+
np.random.seed(0)
6976
perfplot.plot(
70-
setup=lambda n: (numpy.random.rand(n, 100), numpy.random.rand(100, n)),
77+
setup=lambda n: (np.random.rand(n, 100), np.random.rand(100, n)),
7178
kernels=[
72-
lambda xy: numpy.dot(*xy),
79+
lambda xy: np.dot(*xy),
7380
lambda xy: accupy.kdot(*xy, K=2),
7481
lambda xy: accupy.kdot(*xy, K=3),
7582
lambda xy: accupy.fdot(*xy),
7683
],
77-
labels=["numpy.dot", "accupy.kdot[2]", "accupy.kdot[3]", "accupy.fdot"],
84+
labels=["np.dot", "accupy.kdot[2]", "accupy.kdot[3]", "accupy.fdot"],
7885
n_range=n_range,
7986
xlabel="n",
8087
)
@@ -85,16 +92,16 @@ def test_speed_comparison2(n_range=None):
8592
if n_range is None:
8693
n_range = [2 ** k for k in range(2)]
8794

88-
numpy.random.seed(0)
95+
np.random.seed(0)
8996
perfplot.plot(
90-
setup=lambda n: (numpy.random.rand(100, n), numpy.random.rand(n, 100)),
97+
setup=lambda n: (np.random.rand(100, n), np.random.rand(n, 100)),
9198
kernels=[
92-
lambda xy: numpy.dot(*xy),
99+
lambda xy: np.dot(*xy),
93100
lambda xy: accupy.kdot(*xy, K=2),
94101
lambda xy: accupy.kdot(*xy, K=3),
95102
lambda xy: accupy.fdot(*xy),
96103
],
97-
labels=["numpy.dot", "accupy.kdot[2]", "accupy.kdot[3]", "accupy.fdot"],
104+
labels=["np.dot", "accupy.kdot[2]", "accupy.kdot[3]", "accupy.fdot"],
98105
n_range=n_range,
99106
xlabel="n",
100107
logx=True,
@@ -104,8 +111,8 @@ def test_speed_comparison2(n_range=None):
104111

105112

106113
def test_discontiguous():
107-
x = numpy.random.rand(3, 10)
108-
y = numpy.random.rand(3, 10)
114+
x = np.random.rand(3, 10)
115+
y = np.random.rand(3, 10)
109116
accupy.kdot(x.T, y)
110117
accupy.fdot(x.T, y)
111118

tests/test_sums.py

+30-24
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import dufte
22
import matplotlib.pyplot as plt
3-
import numpy
3+
import numpy as np
44
import perfplot
55
import pytest
66

77
import accupy
88

99

10+
def test_cond():
11+
cond = accupy.cond([1.0, 1.0e-16, -1.0])
12+
ref = 2.0e16
13+
assert abs(cond - ref) < 1.0e-13 * abs(ref)
14+
15+
1016
@pytest.mark.parametrize("cond", [1.0, 1.0e15])
1117
def test_ksum2(cond):
1218
p, ref, _ = accupy.generate_ill_conditioned_sum(100, cond)
@@ -33,32 +39,32 @@ def test_accuracy_comparison_illcond(target_conds=None):
3339

3440
kernels = [
3541
sum,
36-
numpy.sum,
42+
np.sum,
3743
accupy.kahan_sum,
3844
lambda p: accupy.ksum(p, K=2),
3945
lambda p: accupy.ksum(p, K=3),
4046
accupy.fsum,
4147
]
4248
labels = [
4349
"sum",
44-
"numpy.sum",
50+
"np.sum",
4551
"accupy.kahan_sum",
4652
"accupy.ksum[2]",
4753
"accupy.ksum[3]",
4854
"accupy.fsum",
4955
]
5056
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"][: len(labels)]
5157

52-
data = numpy.empty((len(target_conds), len(kernels)))
53-
condition_numbers = numpy.empty(len(target_conds))
54-
numpy.random.seed(0)
58+
data = np.empty((len(target_conds), len(kernels)))
59+
condition_numbers = np.empty(len(target_conds))
60+
np.random.seed(0)
5561
for k, target_cond in enumerate(target_conds):
5662
p, ref, C = accupy.generate_ill_conditioned_sum(1000, target_cond)
5763
condition_numbers[k] = C
5864
data[k] = [abs(kernel(p) - ref) / abs(ref) for kernel in kernels]
5965

6066
# sort
61-
s = numpy.argsort(condition_numbers)
67+
s = np.argsort(condition_numbers)
6268
condition_numbers = condition_numbers[s]
6369
data = data[s]
6470

@@ -77,20 +83,20 @@ def test_speed_comparison1(n_range=None):
7783
if n_range is None:
7884
n_range = [2 ** k for k in range(2)]
7985

80-
numpy.random.seed(0)
86+
np.random.seed(0)
8187
perfplot.plot(
82-
setup=lambda n: numpy.random.rand(n, 100),
88+
setup=lambda n: np.random.rand(n, 100),
8389
kernels=[
8490
sum,
85-
lambda p: numpy.sum(p, axis=0),
91+
lambda p: np.sum(p, axis=0),
8692
accupy.kahan_sum,
8793
lambda p: accupy.ksum(p, K=2),
8894
lambda p: accupy.ksum(p, K=3),
8995
accupy.fsum,
9096
],
9197
labels=[
9298
"sum",
93-
"numpy.sum",
99+
"np.sum",
94100
"accupy.kahan_sum",
95101
"accupy.ksum[2]",
96102
"accupy.ksum[3]",
@@ -108,20 +114,20 @@ def test_speed_comparison2(n_range=None):
108114
if n_range is None:
109115
n_range = [2 ** k for k in range(2)]
110116

111-
numpy.random.seed(0)
117+
np.random.seed(0)
112118
perfplot.plot(
113-
setup=lambda n: numpy.random.rand(100, n),
119+
setup=lambda n: np.random.rand(100, n),
114120
kernels=[
115121
sum,
116-
lambda p: numpy.sum(p, axis=0),
122+
lambda p: np.sum(p, axis=0),
117123
accupy.kahan_sum,
118124
lambda p: accupy.ksum(p, K=2),
119125
lambda p: accupy.ksum(p, K=3),
120126
accupy.fsum,
121127
],
122128
labels=[
123129
"sum",
124-
"numpy.sum",
130+
"np.sum",
125131
"accupy.kahan_sum",
126132
"accupy.ksum[2]",
127133
"accupy.ksum[3]",
@@ -134,25 +140,25 @@ def test_speed_comparison2(n_range=None):
134140

135141

136142
def test_knuth_sum():
137-
a16 = numpy.float16(1.0e1)
138-
b16 = numpy.float16(1.0e-1)
143+
a16 = np.float16(1.0e1)
144+
b16 = np.float16(1.0e-1)
139145
x16, y16 = accupy.knuth_sum(a16, b16)
140-
xy = numpy.float64(x16) + numpy.float64(y16)
141-
ab = numpy.float64(a16) + numpy.float64(b16)
146+
xy = np.float64(x16) + np.float64(y16)
147+
ab = np.float64(a16) + np.float64(b16)
142148
assert abs(xy - ab) < 1.0e-15 * ab
143149

144150

145151
def test_decker_sum():
146-
a16 = numpy.float16(1.0e1)
147-
b16 = numpy.float16(1.0e-1)
152+
a16 = np.float16(1.0e1)
153+
b16 = np.float16(1.0e-1)
148154
x16, y16 = accupy.decker_sum(a16, b16)
149-
xy = numpy.float64(x16) + numpy.float64(y16)
150-
ab = numpy.float64(a16) + numpy.float64(b16)
155+
xy = np.float64(x16) + np.float64(y16)
156+
ab = np.float64(a16) + np.float64(b16)
151157
assert abs(xy - ab) < 1.0e-15 * ab
152158

153159

154160
def test_discontiguous():
155-
x = numpy.random.rand(3, 10).T
161+
x = np.random.rand(3, 10).T
156162
accupy.ksum(x.T)
157163
accupy.fsum(x.T)
158164

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ deps =
99
pytest
1010
pytest-cov
1111
commands =
12-
pytest --cov {envsitepackagesdir}/accupy --cov-report xml --cov-report term
12+
pytest {posargs}

0 commit comments

Comments
 (0)