On Aug 26, 2014, at 5:39 PM, Michael Betancourt <
betan...@gmail.com> wrote:
>> If you have exp(NaN) throw exceptions, you'll get different behaviors
>> with double and autodiff objects.
>
> Yes, we will. And I'm fine by that.
>
> I don't want to deal with the abstract minutia off ill-defined specs only to
> throw an error after the expression graph has been built. We might as
> well err as soon as a problem arises.
The C++ spec for exp isn't ill defined! It's clearly defined to
return NaN on NaN input.
The main problem in not following the spec is that it means people
can have code that runs with doubles and produces reasonable outputs
but can't run with Nomad. For instance, it might execute two
statements and if statement a produces NaN use statement b. This is
fine and fine for auto-diff, just not an optimal way to code things.
>> Some people and companies (e.g., Google) have a strict
>> no-exceptions policy in code and libraries they use.
>
> Yup. I also don't know of any other way to handle this kind of run-time
> error without worrying about the propagation of NaNs. Also, no way
> of identifying where the expression graph went sour.
I agree. We originally went down the route in Stan of having everything
use Boost's traits for errors, so that we could get either behavior (return
NaN or raise exception) depending on situation. Finally we decided to
drop potential users of the library and specialize to just exceptions.
I'm still of two minds about all this. It's just so damn complex with the
traits-based error handling, especially before C++11.
>> I suppose you could argue that different types of error messages might
>> be keyed off different types of exceptions, too.
>
> That is the motivation.
Usually the what() message indicates what the exception is.
>> What's a valid input if not one for which a value other than NaN arises?
>
> Within the domain of the function.
I see --- this is the same beef as with the doc Daniel's suggesting.
There are two domains in play. For exp(x), the mathematical function
is defined for x in (-inf, inf) and the output is in (0,inf). The
floating-point implementation is defined for all the floating point
numbers, including NaN and +/- inf. For +inf it returns +inf,
for -inf it returns 0 and for NaN it returns NaN. Furthermore, for
values outside of roughly (-800,800) it also returns NaN.
>> And what about overflows and underflows? For example, exp(1000)
>> overflows to +infinity, and exp(-1000) underflows to 0. Do you want
>> to just return 0 and a gradient of 0? Or raise an error?
>
> 0 + 0. I don't find underflow to be a particularly worrisome error until
> someone tries to invert, in which case I'd argue it's more naturally a
> domain error of the inversion.
What's inversion? You mean log? Underflow's a problem if people naively
multiply a bunch of probabilities together. There's no relevant inversion
anywhere.
>> Of course, for some operations, like factoring a matrix, there's
>> no way to validate the input is valid without running the function.
>
> Not entirely true -- in some cases you can check validity faster
> than performing the decomposition. But in any case the focus
> is on scalar functions here.
OK. But in some cases you can't. Or even if you could do it faster,
you don't want to do the fast thing to check, then call the function that
has the check built in.
>> I don't see how enable_if helps here. If you use some kind of
>> branch-on-template (like we do with traits to eliminate constant terms),
>> then it should get compiled away.
>
> Ugh, I meant builtin-expect. Again, the big issue is whether or not
> the addition of a check, even with builtin-expect, will prevent the
> compiler from inlining a bunch of code and causing noticeable
> slowdowns.
That I don't know the answer to. Most likely it would block inlining
and more importantly locality of code, because code still needs to
be generated for both branches, just in different orders.
> This was a big issue with the stack expansion
> checks. I think the functions are big enough that they're
> not being inlined anywhere, but it's hard to know a priori.
If you check (x + y) for x or y being NaN, that's a pretty big
hit relative to the evaluation cost.
> Biggest question is still unanswered -- validate inputs only (requires
> very strict checks and worry about things like overflow) or just have
> the inputs validated for NaNs and domain errors and check again
> for any divergences in the outputs?
I'd say check up front when you can and when it's not too computationally
expensive. That gives you finer granularity on reporting what went wrong.
But if it's too expensive, then it's probably not worth the finer grain
of reporting.
- Bob