Skip to content

Commit 70c4bda

Browse files
committed
Add cosine_taylor_dekker
1 parent 6880b93 commit 70c4bda

File tree

4 files changed

+145
-60
lines changed

4 files changed

+145
-60
lines changed

functional_algorithms/expr.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,8 @@ def __str__(self):
624624
def __repr__(self):
625625
if self.kind in {"symbol"}:
626626
operands = tuple(repr(o) for o in self.operands)
627+
elif self.kind in {"series", "constant"}:
628+
operands = (repr(self.operands[0]),) + tuple(f"{o.kind}:{o.intkey}" for o in self.operands[1:])
627629
else:
628630
operands = tuple(f"{o.kind}:{o.intkey}" for o in self.operands)
629631
return f"{type(self).__name__}({self.kind!r}, {operands}, {self.props})"

functional_algorithms/floating_point_algorithms.py

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,8 @@ def mul_dw(ctx, xh, xl, yh, yl):
305305
def div_dw(ctx, xh, xl, yh, yl):
306306
"""Dekker's division"""
307307
q = xh / yh
308-
th, tl = mul_dw(q, yh)
309-
l = (xh - th - tl + xl - q * yl) / yh
308+
th, tl = mul_dekker(ctx, q, yh)
309+
l = ((((xh - th) - tl) + xl) - q * yl) / yh
310310
rh = q + l
311311
rl = q - rh + l
312312
return rh, rl
@@ -1447,26 +1447,11 @@ def sine_taylor(ctx, x, order=7, split=False):
14471447
return p0, p1 * xxl
14481448

14491449

1450-
def sine_taylor_dekker(ctx, x, order=7):
1451-
"""Return sine of x using Taylor series approximation and Dekker's product.
1452-
1453-
See also sine_taylor.
1454-
"""
1455-
C, f = [x], 1
1456-
for i in range(3, order + 1, 2):
1457-
f *= -i * (i - 1)
1458-
C.append(div_series(ctx, x, ctx.constant(f, x)))
1459-
xx = mul_series(ctx, x, x)
1460-
# Horner's scheme is most accurate
1461-
return fast_polynomial_dekker(
1462-
ctx, xx, C, reverse=False, scheme=[None, horner_scheme, estrin_dac_scheme, canonical_scheme][1]
1463-
)
1464-
1465-
14661450
def cosine_taylor(ctx, x, order=6, split=False, drop_leading_term=False):
14671451
"""Return sine of x using Taylor series approximation:
14681452
14691453
C(x) = 1 - x ** 2 / 2 + x ** 4 / 24 - ... + O(x ** (order + 2))
1454+
= 1 + x**2 * P(x**2, C=[-1/2!, 1/4!, -1/6!, ...])
14701455
14711456
If split is True, return `ch, cl` such that
14721457
@@ -1556,6 +1541,51 @@ def cosine_taylor(ctx, x, order=6, split=False, drop_leading_term=False):
15561541
return ctx.constant(1, x) + p0, p1 * xxl
15571542

15581543

1544+
def sine_taylor_dekker(ctx, x, order=7):
1545+
"""Return sine of x using Taylor series approximation and Dekker's product.
1546+
1547+
See also sine_taylor.
1548+
"""
1549+
C, f = [x], 1
1550+
for i in range(3, order + 1, 2):
1551+
f *= -i * (i - 1)
1552+
C.append(div_series(ctx, x, ctx.constant(f, x)))
1553+
xx = mul_series(ctx, x, x)
1554+
# Horner's scheme is most accurate
1555+
return fast_polynomial_dekker(
1556+
ctx, xx, C, reverse=False, scheme=[None, horner_scheme, estrin_dac_scheme, canonical_scheme][1]
1557+
)
1558+
1559+
1560+
def cosine_taylor_dekker(ctx, x, order=7, drop_leading_term=False):
1561+
"""Return cosine of x using Taylor series approximation and Dekker's product.
1562+
1563+
See also cosine_taylor.
1564+
"""
1565+
one = ctx.constant(1, x)
1566+
xx = mul_series(ctx, x, x)
1567+
if not drop_leading_term:
1568+
# P(x**2, Ce=[1, -1/2!, 1/4!, -1/6!, ...])
1569+
C, f = [one], 1
1570+
for i in range(2, order + 1, 2):
1571+
f *= -i * (i - 1)
1572+
C.append(div_series(ctx, one, ctx.constant(f, x)))
1573+
# Horner's scheme is most accurate
1574+
return fast_polynomial_dekker(
1575+
ctx, xx, C, reverse=False, scheme=[None, horner_scheme, estrin_dac_scheme, canonical_scheme][1]
1576+
)
1577+
else:
1578+
# x**2 * P(x**2, C=[-1/2!, 1/4!, -1/6!, ...])
1579+
C, f = [], 1
1580+
for i in range(2, order + 1 - 2, 2):
1581+
f *= -i * (i - 1)
1582+
C.append(div_series(ctx, xx, ctx.constant(f, x)))
1583+
# Horner's scheme is most accurate
1584+
return fast_polynomial_dekker(
1585+
ctx, xx, C, reverse=False, scheme=[None, horner_scheme, estrin_dac_scheme, canonical_scheme][1]
1586+
)
1587+
1588+
15591589
def sine_pade(ctx, x, variant=None):
15601590
# See https://math.stackexchange.com/questions/2196371/how-to-approximate-sinx-using-pad%C3%A9-approximation
15611591

functional_algorithms/rewrite.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import numpy
33
from collections import defaultdict
44
from . import expr as _expr
5-
from .utils import number_types, value_types, float_types, complex_types, boolean_types
5+
from .utils import number_types, value_types, float_types, complex_types, boolean_types, warn_once
66

77

88
class Printer:
@@ -478,6 +478,8 @@ def _binary_op(self, expr, op):
478478
yvalue, ylike = y.operands
479479
if isinstance(xvalue, number_types) and isinstance(yvalue, number_types):
480480
r = op(xvalue, yvalue)
481+
if numpy.isfinite(xvalue) and numpy.isfinite(yvalue) and not numpy.isfinite(r):
482+
warn_once(f"{expr} evaluation resulted a non-finite value `{r}`")
481483
return expr.context.constant(r, xlike)
482484

483485
def add(self, expr):

functional_algorithms/tests/test_floating_point_algorithms.py

Lines changed: 92 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -894,66 +894,117 @@ def test_sine_taylor_dekker(dtype):
894894

895895

896896
@pytest.mark.parametrize(
897-
"func,fma", [("cos", "upcast"), ("cos", "mul_add"), ("cos", "native"), ("cosm1", "upcast"), ("numpy.cos", None)]
897+
"func,fma",
898+
[
899+
("cos", "upcast"),
900+
("cos", "mul_add"),
901+
("cos", "native"),
902+
("cos_dekker", "native"),
903+
("cos_numpy", None),
904+
("cosm1_dekker", "native"),
905+
("cosm1", "upcast"),
906+
("cosm1_sin", "upcast"),
907+
("cosm1_sin_numpy", None),
908+
("cosm1_numpy", None),
909+
],
898910
)
899911
def test_cosine_taylor(dtype, fma, func):
900912
import mpmath
901913
from collections import defaultdict
902914

903915
t_prec = utils.get_precision(dtype)
904916
working_prec = {11: 50 * 4, 24: 50 * 4, 53: 74 * 16}[t_prec]
905-
optimal_order = {11: 9, 24: 11, 53: 17}[t_prec]
917+
optimal_order = {11: 9, 24: 13, 53: 19}[t_prec]
906918
size = 1000
907919
samples = list(utils.real_samples(size, dtype=dtype, min_value=dtype(0), max_value=dtype(numpy.pi / 4)))
908920
size = len(samples)
909921
with mpmath.mp.workprec(working_prec):
910922
mpctx = mpmath.mp
911923
for order in [optimal_order, 1, 3, 5, 7, 9, 11, 13, 17, 19][:1]:
912924

913-
@fa.targets.numpy.jit(
914-
paths=[fpa],
915-
dtype=dtype,
916-
debug=(1.5 if size <= 10 else 0),
917-
rewrite_parameters=dict(optimize_cast=False, fma_backend=fma),
918-
)
919-
def cos_func(ctx, x):
920-
return fpa.cosine_taylor(ctx, x, order=order, split=False)
921-
922-
@fa.targets.numpy.jit(
923-
paths=[fpa],
924-
dtype=dtype,
925-
debug=(1.5 if size <= 10 else 0),
926-
rewrite_parameters=dict(optimize_cast=False, fma_backend=fma),
927-
)
928-
def cosm1_func(ctx, x):
929-
return fpa.cosine_taylor(ctx, x, order=order, split=False, drop_leading_term=True)
925+
if func.startswith("cosm1"):
926+
927+
def f_expected(x):
928+
return mpctx.cos(x) - 1
929+
930+
else:
931+
932+
def f_expected(x):
933+
return mpctx.cos(x)
934+
935+
if func in {"cos_dekker", "cosm1_dekker"}:
936+
937+
@fa.targets.numpy.jit(
938+
paths=[fpa],
939+
dtype=dtype,
940+
debug=(1.5 if size <= 10 else 0),
941+
parameters=dict(series_uses_dekker=True, series_uses_2sum=True),
942+
)
943+
def f(ctx, x):
944+
return fpa.cosine_taylor_dekker(ctx, x, order=order, drop_leading_term=func.startswith("cosm1"))
945+
946+
elif func == "cos":
947+
948+
@fa.targets.numpy.jit(
949+
paths=[fpa],
950+
dtype=dtype,
951+
debug=(1.5 if size <= 10 else 0),
952+
rewrite_parameters=dict(optimize_cast=False, fma_backend=fma),
953+
)
954+
def f(ctx, x):
955+
return fpa.cosine_taylor(ctx, x, order=order, split=False)
956+
957+
elif func == "cosm1":
958+
959+
@fa.targets.numpy.jit(
960+
paths=[fpa],
961+
dtype=dtype,
962+
debug=(1.5 if size <= 10 else 0),
963+
rewrite_parameters=dict(optimize_cast=False, fma_backend=fma),
964+
)
965+
def f(ctx, x):
966+
return fpa.cosine_taylor(ctx, x, order=order, split=False, drop_leading_term=True)
967+
968+
elif func == "cosm1_sin":
969+
970+
@fa.targets.numpy.jit(
971+
paths=[fpa],
972+
dtype=dtype,
973+
debug=(1.5 if size <= 10 else 0),
974+
rewrite_parameters=dict(optimize_cast=False, fma_backend=fma),
975+
)
976+
def f(ctx, x):
977+
two = ctx.constant(2, x)
978+
sn = fpa.sine_taylor(ctx, x / two, order=order, split=False)
979+
return -two * sn * sn
980+
981+
elif func == "cos_numpy":
982+
983+
f = numpy.cos
984+
985+
elif func == "cosm1_numpy":
986+
987+
def f(x):
988+
return numpy.cos(x) - dtype(1)
989+
990+
elif func == "cosm1_sin_numpy":
991+
992+
def f(x):
993+
two = dtype(2)
994+
sn = numpy.sin(x / two)
995+
return -two * sn * sn
996+
997+
else:
998+
assert 0, func # not impl
930999

9311000
ulp = defaultdict(int)
9321001
for x in samples:
933-
expected_cs = utils.mpf2float(dtype, mpctx.cos(utils.float2mpf(mpctx, x)))
934-
if func == "numpy.cos":
935-
cs = numpy.cos(x)
936-
u = utils.diff_ulp(cs, expected_cs)
937-
elif func == "cos":
938-
cs = cos_func(x)
939-
"""
940-
csh, csl = fpa.cosine_taylor(ctx, x, order=order, split=True)
941-
cs2 = utils.mpf2float(dtype, utils.float2mpf(mpctx, csh) + utils.float2mpf(mpctx, csl))
942-
assert cs == cs2, (cs, cs2, expected_cs)
943-
"""
944-
u = utils.diff_ulp(cs, expected_cs)
945-
elif func == "cosm1":
946-
expected_csm1 = utils.mpf2float(dtype, mpctx.cos(utils.float2mpf(mpctx, x)) - 1)
947-
cs = cosm1_func(x)
948-
u = utils.diff_ulp(cs, expected_csm1, flush_subnormals=True)
949-
else:
950-
assert 0, func # unreachable
1002+
expected = utils.mpf2float(dtype, f_expected(utils.float2mpf(mpctx, x)))
1003+
cs = f(x)
1004+
u = utils.diff_ulp(cs, expected)
9511005
ulp[u] += 1
9521006

953-
if 0:
954-
c = "." if u == 0 else ("v" if cs < expected_cs else "^")
955-
print(c, end="", flush=True)
956-
1007+
print()
9571008
show_ulp(ulp)
9581009

9591010

0 commit comments

Comments
 (0)