Skip to content

Issue 1232 - variadic is_nan function#1233

Merged
syclik merged 21 commits intostan-dev:developfrom
andrjohns:feature/issue-1232-variadic-is-nan
Jul 9, 2019
Merged

Issue 1232 - variadic is_nan function#1233
syclik merged 21 commits intostan-dev:developfrom
andrjohns:feature/issue-1232-variadic-is-nan

Conversation

@andrjohns
Copy link
Collaborator

Summary

As initially described in #1232 - Several functions call is_nan multiple times using the || operator, such as in prim/scal/fun/grad_reg_inc_gamma:

  if (is_nan(a) || is_nan(z) || is_nan(g) || is_nan(dig))
    return std::numeric_limits<TP>::quiet_NaN();

This PR adds a variadic extension to the is_nan function so that multiple inputs can be passed in a single call

  if (is_nan(a, z, g, dig))
    return std::numeric_limits<TP>::quiet_NaN();

Tests

Added tests for calling is_nan with multiple inputs

Side Effects

N/A

Checklist

  • Math issue Variadic is_nan function #1232

  • Copyright holder: Andrew Johnson

    The copyright holder is typically you or your assignee, such as a university or company. By submitting this pull request, the copyright holder is agreeing to the license the submitted work under the following licenses:
    - Code: BSD 3-clause (https://opensource.org/licenses/BSD-3-Clause)
    - Documentation: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

  • the basic tests are passing

    • unit tests pass (to run, use: ./runTests.py test/unit)
    • header checks pass, (make test-headers)
    • docs build, (make doxygen)
    • code passes the built in C++ standards checks (make cpplint)
  • the code is written in idiomatic C++ and changes are documented in the doxygen

  • the new changes are tested

@rok-cesnovar rok-cesnovar self-requested a review May 12, 2019 16:51
Copy link
Member

@syclik syclik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting a quick doc change for clarity. Everything else looks awesome!

}

/**
* Returns true if the input is NaN and false otherwise.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this doc more precise? It should indicate that it returns true if any of the input is NaN (as opposed to all).

@wds15
Copy link
Contributor

wds15 commented May 13, 2019

I think what @bob-carpenter said is important: The function should become is_nan_any or something similar.

@andrjohns
Copy link
Collaborator Author

@syclik I've made the changes from the issue discussion and the tests have passed, this is ready for re-review

Copy link
Member

@syclik syclik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a few comments. Overall, this looks great! The major things we need to address:

  1. is_any_nan has all its arguments pass by value and it should really default to pass by reference. (Unless the T is actually being deduced as const stan::math::var& when appropriate... if that's happening, just let me know.)
  2. there's inconsistency with the prim version of is_nan and the other function overloads. They should all be function overloads or shift to a templated function with template specializations.

* Returns true if the input is NaN and false otherwise.
*
* Delegates to <code>stan::math::is_nan</code> so that
* appropriate specialisations can be loaded for autodiff
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: mind spelling “specialisations” as “specializations”? We’ve used American English throughout our docs and it’d be good to maintain consistency.

* @return <code>true</code> if the value is NaN.
*/
template <typename T>
inline bool is_any_nan(T x) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have caught this on the original PR that introduced is_nan. The arguments are passed by value here. They should be passed by reference for efficiency reasons. So... in the function signature: is_any_nan(T& x) instead of is_any_nan(T x).

* @return <code>true</code> if any value is NaN
*/
template <typename T, typename... Ts>
inline bool is_any_nan(T x, Ts... xs) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. This should be by reference.

*/
inline bool is_nan(double x) { return std::isnan(x); }
template <typename T>
inline bool is_nan(T x) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... this looks odd. In develop, we have 3 definitions of is_nan: prim/scal, rev/scal, fwd/scal. They are implemented using function overloading.

I think you either want to go the same route or you want to make this function the template function (as you have it) and then change the implementations in rev and fwd to be template specializations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that make sense? If it's not clear what I'm talking about, we can discuss.

inline typename return_type<T1, T2>::type log_falling_factorial(const T1 x,
const T2 n) {
if (is_nan(x) || is_nan(n))
if (is_any_nan(x, n))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[comment]
The changes look awesome! It does look like there's an impact on how we will write functions here on out.

@andrjohns
Copy link
Collaborator Author

@serban-nicusor-toptal would you be able to restart the "Always-run tests part 1" for this pull? Looks like the failure was just randomness, as discussed in #1238

@serban-nicusor-toptal
Copy link
Contributor

@andrjohns Good morning!
I've restarted it, it's wating for a gpu machine. Keep me updated!

@andrjohns
Copy link
Collaborator Author

Thanks for that!

@andrjohns
Copy link
Collaborator Author

@serban-nicusor-toptal looks like the build is running twice for some reason (there's a run 8 and a run 9), not sure what happened there

@rok-cesnovar
Copy link
Member

I got the email and restarted it. Should have posted here sorry.

@andrjohns
Copy link
Collaborator Author

No worries, I'm not about to complain for having too much help!

@stan-buildbot
Copy link
Contributor

(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 1.03)
(stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan, 0.99)
(stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan, 1.01)
(stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan, 1.02)
(stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan, 0.99)
(stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan, 0.99)
(stat_comp_benchmarks/benchmarks/arK/arK.stan, 1.04)
(performance.compilation, 1.0)
(stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan, 0.98)
(stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan, 1.0)
(stat_comp_benchmarks/benchmarks/sir/sir.stan, 1.05)
(stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan, 1.02)
(stat_comp_benchmarks/benchmarks/garch/garch.stan, 1.06)
(stat_comp_benchmarks/benchmarks/gp_regr/gen_gp_data.stan, 0.99)
(stat_comp_benchmarks/benchmarks/arma/arma.stan, 1.21)
Result: 1.02440163632
Commit hash: 313b284

@stan-buildbot
Copy link
Contributor

(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 1.0)
(stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan, 1.01)
(stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan, 1.0)
(stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan, 1.0)
(stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan, 0.99)
(stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan, 1.01)
(stat_comp_benchmarks/benchmarks/arK/arK.stan, 1.0)
(performance.compilation, 1.0)
(stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan, 0.98)
(stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan, 0.99)
(stat_comp_benchmarks/benchmarks/sir/sir.stan, 0.99)
(stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan, 1.0)
(stat_comp_benchmarks/benchmarks/garch/garch.stan, 1.01)
(stat_comp_benchmarks/benchmarks/gp_regr/gen_gp_data.stan, 1.01)
(stat_comp_benchmarks/benchmarks/arma/arma.stan, 0.99)
Result: 0.99985616572
Commit hash: 5f1dcb7

@andrjohns
Copy link
Collaborator Author

@syclik This is ready for another look-over. I've updated is_any_nan to pass by reference and changed the rev/fwd versions of is_nan to template specialisations.

I also added in fwd & rev tests for is_any_nan, just to be safe

bob-carpenter
bob-carpenter previously approved these changes May 20, 2019
Copy link
Member

@bob-carpenter bob-carpenter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So as not to overreview this and have you wait for my approval, I'm just going to click approve here. But there are some things that should be changed.

Thanks for taking all this on! Overall, these changes are big improvements. I'm just worrying about minor details here that will be important for consistency.

*/
template <typename T>
inline int is_nan(const fvar<T>& x) {
inline typename std::enable_if_t<is_fvar<T>::value, bool> is_nan(const T& x) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the motivation for switching to enable_if_t from the simple pattern matching? I think this signature should just be:

template <typename T>
inline bool is_nan(const fvar<T>& x);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was one was based on @syclik's review:

Hmm... this looks odd. In develop, we have 3 definitions of is_nan: prim/scal, rev/scal, fwd/scal. They are implemented using function overloading.

I think you either want to go the same route or you want to make this function the template function (as you have it) and then change the implementations in rev and fwd to be template specializations.

*/
template <typename T, typename... Ts>
inline bool is_any_nan(const T& x, const Ts&... xs) {
return (is_any_nan(x) || is_any_nan(std::forward<const Ts>(xs)...));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are both OK to be constant references so they'll be generic. You don't need those outer parens---that's an R thing, not a C++ thing and they're just a distraction here.

*/
inline bool is_nan(double x) { return std::isnan(x); }
template <typename T>
inline typename std::enable_if_t<std::is_arithmetic<T>::value, bool> is_nan(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here as in the issue for changing all of these. At most, this should be

template <typename T>
inline typename std::enable_if_t<std::is_arithmetic<T>::value, bool> is_nan(T x) {
  return std::isnan(x);
}

so primitives get passed by value.

@stan-buildbot
Copy link
Contributor

(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 1.0)
(stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan, 0.97)
(stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan, 1.0)
(stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan, 0.99)
(stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan, 1.0)
(stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan, 0.99)
(stat_comp_benchmarks/benchmarks/arK/arK.stan, 1.0)
(performance.compilation, 1.01)
(stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan, 0.98)
(stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan, 0.99)
(stat_comp_benchmarks/benchmarks/sir/sir.stan, 1.03)
(stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan, 1.0)
(stat_comp_benchmarks/benchmarks/garch/garch.stan, 1.01)
(stat_comp_benchmarks/benchmarks/gp_regr/gen_gp_data.stan, 0.98)
(stat_comp_benchmarks/benchmarks/arma/arma.stan, 0.99)
Result: 0.99704847431
Commit hash: 31f33be

@andrjohns
Copy link
Collaborator Author

@syclik This one is just waiting on your approval before it's ready to merge, thanks!

Copy link
Member

@syclik syclik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrjohns: most of it is good. I think there was confusion about how is_nan() should be implemented. I have the same question that @bob-carpenter had. Why do we need the enable_if_t at all?

In prim, it could be:

template <typename T>
inline bool is_nan(T x) {
   return std::isnan(x);
}

in fwd:

template <>
template <typename T>
inline bool is_nan(const fvar<T>& x) {
   return is_nan(x.val());
}

and rev:

template <>
inline bool is_nan(const var& x) {
  return is_nan(x.val());
}

Is there something wrong with the approach above? In the Math library, where we can, we just use the template specializations directly rather than trying to do clever trait programming. Simpler is better for reading this code.

@bob-carpenter
Copy link
Member

bob-carpenter commented May 28, 2019 via email

@syclik
Copy link
Member

syclik commented May 30, 2019

@bob-carpenter: I'm with you. I sort of view them as exchangeable for this sort of application (although someone might be able to tell me why one would be preferred). What I was trying to get at with my comments was that we don't need the enable_if type of construction for this use case. It's just easier to read and follow when it's less complex.

@andrjohns
Copy link
Collaborator Author

@syclik @bob-carpenter
Should I just leave is_nan for autodiff types unchanged from develop? Seems like there's no preference either way, so I don't think there's much need to change it

@bob-carpenter
Copy link
Member

bob-carpenter commented May 31, 2019 via email

@drezap
Copy link
Member

drezap commented Jun 19, 2019

@syclik it's your call ^^^^^ (just trying to keep things moving. this is a good change).

@rok-cesnovar
Copy link
Member

@andrjohns when you have the time, please merge the latest develop in. There are conflicts due to the meta flattening refactor.

@syclik syclik self-assigned this Jun 20, 2019
@stan-buildbot
Copy link
Contributor

(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 0.99)
(stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan, 1.01)
(stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan, 1.0)
(stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan, 1.01)
(stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan, 0.98)
(stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan, 1.0)
(stat_comp_benchmarks/benchmarks/arK/arK.stan, 1.0)
(performance.compilation, 1.02)
(stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan, 0.98)
(stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan, 1.0)
(stat_comp_benchmarks/benchmarks/sir/sir.stan, 0.99)
(stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan, 0.99)
(stat_comp_benchmarks/benchmarks/garch/garch.stan, 1.01)
(stat_comp_benchmarks/benchmarks/gp_regr/gen_gp_data.stan, 0.99)
(stat_comp_benchmarks/benchmarks/arma/arma.stan, 0.98)
Result: 0.99702892937
Commit hash: ca786de

@andrjohns
Copy link
Collaborator Author

@syclik Did you have a preference on the specification of is_nan for autodiff types (i.e. leave unchanged from develop or enable_if templating)?

@syclik
Copy link
Member

syclik commented Jun 30, 2019

My preference is for simpler rather than more complex. Here, I'd prefer develop rather than using enable_if.

@andrjohns
Copy link
Collaborator Author

Great! Changes have been reverted

@andrjohns
Copy link
Collaborator Author

@serban-nicusor-toptal If the windows threading errors aren't an issue anymore can you restart the testing for this one? Thanks

@stan-buildbot
Copy link
Contributor

(stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan, 1.0)
(stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan, 1.02)
(stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan, 0.97)
(stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan, 0.99)
(stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan, 0.98)
(stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan, 1.01)
(stat_comp_benchmarks/benchmarks/arK/arK.stan, 1.0)
(performance.compilation, 1.01)
(stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan, 1.0)
(stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan, 1.0)
(stat_comp_benchmarks/benchmarks/sir/sir.stan, 1.05)
(stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan, 0.99)
(stat_comp_benchmarks/benchmarks/garch/garch.stan, 1.02)
(stat_comp_benchmarks/benchmarks/gp_regr/gen_gp_data.stan, 0.97)
(stat_comp_benchmarks/benchmarks/arma/arma.stan, 1.0)
Result: 1.00166991255
Commit hash: 66f8df6

Copy link
Member

@syclik syclik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!!!

@syclik syclik merged commit c11a3d7 into stan-dev:develop Jul 9, 2019
@andrjohns andrjohns deleted the feature/issue-1232-variadic-is-nan branch July 25, 2019 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants