Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong hyperbolic tangent for argument >> 0 #37

Closed
afossa opened this issue Sep 15, 2022 · 2 comments
Closed

Wrong hyperbolic tangent for argument >> 0 #37

afossa opened this issue Sep 15, 2022 · 2 comments
Assignees
Labels
analyzed Problem has been identified but no solution has been proposed yet bug

Comments

@afossa
Copy link

afossa commented Sep 15, 2022

Computing the hyperbolic tangent of a DA object with constant part |c0|>>0 returns always zero. The correct behaviour would be +1 for c0 that goes to plus infinity and -1 for c0 that goes to minus infinity.

Examples:
DACE::DA x = 100 + DACE::DA(1)
x.tanh() --> 0 but should be 1

DACE::DA x = -100 + DACE::DA(1)
x.tanh() --> 0 but should be -1

@abgandar
Copy link
Collaborator

abgandar commented Sep 16, 2022

TL;DR

As a work-around, set the DA cutoff value to zero (or something very small) by calling DA::setEps() right after DA::init():

    DA::init( 20, 1 );
    DA::setEps( 1e-300 );

Analysis

DA::tanh() is internally implemented as sinh()/cosh(), which in DA is actually computed as sinh*minv(cosh) where minv(x) is the multiplicative inverse (i.e. 1/x).

With large, but not yet overflowing arguments, the coefficients of cosh become huge (1e40 and larger). Consequently, the multiplicative inverse has coefficients on the order of 1e-40. With current default settings, these get flushed to zero (including the constant part) as they are smaller than the DA epsilon, leaving only the zero DA vector. The subsequent multiplication with the large sinh DA yields the incorrect result of a zero DA.

Discussion

This is another instance of the default DA epsilon (~1e-20) optimization causing unexpected issues. Initially this measure was introduced from COSY as a way to speed up a limited set of computations where

  1. values are normalized such that the constant parts are always on the order of 1,
  2. a significant number of coefficients is known to be zero e.g. due to symmetry or very small expansions, but accumulate small values due to round-off errors.

It is questionable how relevant this optimization is now in the "real world" where code is not specifically optimized for these conditions, and typical DA vectors are most of the time close to fully populated.

Contributing to the problem is the lack of floating point exception handling in the DACE at the moment, as the library is not made for validated computing. This means overflows to infinity, and subsequent division by infinite values will silently produce zeros without error messages to the user. See separate ticket #38.

Partial Solution

A DA epsilon value close to the floating point underflow limit (~ 1e-308) or zero to prevent flushing small but valid coefficients should be made the permanent (safe) default within the DACE, with the potentially unsafe optimization having to be enabled by the user explicitly through DA::setEps() instead. This is a transparent change which at the worst makes existing code legitimately relying on DA epsilon slightly slower.

The above will fix DA::tanh() for arguments up to about 690. Above that, over/underflows happen and
We should also add range checks to DA::sinh() and DA::cosh(), throwing an error if the resulting value is infinity. This will break DA::tanh() for large arguments where it is perfectly well defined, but the only way around that would be to rewrite DA::tanh() to use a direct calculation instead of relying on potentially overflowing intermediary values.

While that solves the immediate problem at hand, it just moves the underlying cause from the DACE library into user code where similar overflow issues might arise depending on how a user implements their equations. A correct solution requires more careful consideration of how to handle floating point exceptions in the DACE which is much harder to do correctly (see ticket #38).

@abgandar abgandar self-assigned this Sep 16, 2022
@abgandar abgandar added bug analyzed Problem has been identified but no solution has been proposed yet labels Sep 16, 2022
@abgandar
Copy link
Collaborator

abgandar commented Oct 9, 2022

Previous commit should fix the issue by changing NaN and Inf handling as well as implementing an improved algorithm for tanh that works for full argument range.

@abgandar abgandar closed this as completed Oct 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
analyzed Problem has been identified but no solution has been proposed yet bug
Projects
None yet
Development

No branches or pull requests

2 participants