Skip to content

Add vectorised select(), any(), and all() functions - #2853

Merged
andrjohns merged 30 commits into
stan-dev:developfrom
andrjohns:vectorised-select
Aug 11, 2023
Merged

andrjohns merged 30 commits into
stan-dev:developfrom
andrjohns:vectorised-select

Conversation

@andrjohns

@andrjohns andrjohns commented Dec 9, 2022 •

Copy link
Copy Markdown
Collaborator

Summary

This PR extracts the vectorised select() function introduced in this PR (which in turn is adding the currently OpenCL-only function to the rest of the Math library), and also introduces the any() and all() helper functions.

The select() function is used for ternary operations that are intended to be agnostic between scalars and containers. If the function is used with a mixture of scalars and containers, then the scalar is promoted to the same size and type as the container.

Tests

Tests are added for all combinations of scalar/container inputs, as well as error checking for mismatched container sizes.

Side Effects

N/A

Release notes

Added select() function for vectorised ternary operations, as well as the any() and all() boolean reduction functions

Checklist

  • Math issue Add select() and any() helper functions #2852

  • 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)
    • dependencies checks pass, (make test-math-dependencies)
    • 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

@andrjohns andrjohns changed the title Add a vectorised select function Add vectorised select(), any(), and all() functions Dec 12, 2022
@andrjohns

Copy link
Copy Markdown
Collaborator Author

I've also added the any() and all() functions to this PR since they were fairly simple in comparison (happy to split to separate PR though!)

@SteveBronder

Copy link
Copy Markdown
Collaborator

Ping me when this is ready for review!

@andrjohns

Copy link
Copy Markdown
Collaborator Author

Ping me when this is ready for review!

Thanks @SteveBronder! This should be good to look at now

@andrjohns

Copy link
Copy Markdown
Collaborator Author

@SteveBronder one more thing I've been thinking about, do we want to start requiring/recommending tuple compatibility for utility functions like these? (probably only really relevant for any(), and all() here)

@SteveBronder

Copy link
Copy Markdown
Collaborator

I'm not sure if it should be a requirement, but it would def be nice!

Comment thread stan/math/prim/fun/select.hpp Outdated
Comment thread stan/math/prim/fun/select.hpp Outdated
Comment thread stan/math/prim/fun/select.hpp
@andrjohns

Copy link
Copy Markdown
Collaborator Author

@SteveBronder just addressed your comments. I've also changed the handling for tuples in all()/any() to use math::for_each() if you wouldn't mind double-checking that you're happy with that over the previous std::vector<bool> initialiser approach

Comment thread stan/math/prim/fun/select.hpp Outdated
template <typename T_true, typename T_false,
require_all_stan_scalar_t<T_true, T_false>* = nullptr>
inline auto select(const bool c, const T_true y_true, const T_false y_false) {
return c ? y_true : y_false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we cast y_true / y_false to the same type explicitly or is it okay if we assume they are implicitly promotable?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we want to do this implicit vs. explicit promotion everywhere

Comment thread stan/math/prim/fun/select.hpp Outdated
Comment on lines +54 to +58
if (c) {
return T_true_plain(y_true);
} else {
return T_true_plain(y_false);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (c) {
return T_true_plain(y_true);
} else {
return T_true_plain(y_false);
}
return c ? T_true_plain(y_true) : T_true_plain(y_false);

Comment thread stan/math/prim/fun/select.hpp
Comment on lines +146 to +148
template <typename T_bool, typename T_true, typename T_false,
require_eigen_array_t<T_bool>* = nullptr,
require_all_stan_scalar_t<T_true, T_false>* = nullptr>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we be more restrictive with T_bool here like require_eigen_array_vt<is_integral, T_bool> (bool is integrable here)

https://godbolt.org/z/shn6hb3ca

Comment thread stan/math/prim/fun/select.hpp

@SteveBronder SteveBronder left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

sry clicked approved by accident. Few qs above!

@SteveBronder

Copy link
Copy Markdown
Collaborator

I'll take a look at this today

@SteveBronder SteveBronder left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Mostly doc fixes but once that's done then I think good to merge!

Comment thread stan/math/prim/fun/all.hpp Outdated
* @param x boolean input
* @return The input unchanged
*/
inline bool all(bool x) { return x; }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(optional) constexpr?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also should this be templated to accept any integral type? Not sure if we would get warnings about downcasting? This applies to all the bool as input functions

Comment thread stan/math/prim/fun/any.hpp Outdated
*
* Overload for Eigen types
*
* @tparam Eigen type of the input

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* @tparam Eigen type of the input
* @tparam ContainerT A type derived from `Eigen::EigenBase` that has an `integral` scalar type

Double check the docs for all these

Comment thread stan/math/prim/fun/select.hpp Outdated
* Return the second argument if the first argument is true
* and otherwise return the third argument.
*
* <code>select(c, y1, y0) = c ? y1 : y0</code>.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(optional) I just prefer using ticks here ` doxygen is able to parse that like it's markdown

Comment thread stan/math/prim/fun/select.hpp Outdated
Comment on lines +47 to +58
template <
typename T_true, typename T_false,
typename T_return = return_type_t<T_true, T_false>,
typename T_true_plain = promote_scalar_t<T_return, plain_type_t<T_true>>,
typename T_false_plain = promote_scalar_t<T_return, plain_type_t<T_false>>,
require_all_container_t<T_true, T_false>* = nullptr,
require_all_same_t<T_true_plain, T_false_plain>* = nullptr>
inline T_true_plain select(const bool c, const T_true y_true,
const T_false y_false) {
check_matching_dims("select", "y_true", y_true, "y_false", y_false);
return c ? T_true_plain(y_true) : T_true_plain(y_false);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(optional) it may be nice to have a signature for when T_true_plain is not constructable from a T_false_plain that has a static assert in it with a nice message.

Comment thread stan/math/prim/fun/select.hpp Outdated
Comment on lines +54 to +58
inline T_true_plain select(const bool c, const T_true y_true,
const T_false y_false) {
check_matching_dims("select", "y_true", y_true, "y_false", y_false);
return c ? T_true_plain(y_true) : T_true_plain(y_false);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If these are containers should they be passed as references like

Suggested change
inline T_true_plain select(const bool c, const T_true y_true,
const T_false y_false) {
check_matching_dims("select", "y_true", y_true, "y_false", y_false);
return c ? T_true_plain(y_true) : T_true_plain(y_false);
}
inline T_true_plain select(const bool c, T_true&& y_true,
T_false&& y_false) {
check_matching_dims("select", "y_true", y_true, "y_false", y_false);
return c ? T_true_plain(std::forward<T_true>(y_true)) : T_true_plain(std::forward<T_false>(y_false));
}

Comment thread stan/math/prim/fun/select.hpp Outdated
require_eigen_array_vt<std::is_integral, T_bool>* = nullptr,
require_all_stan_scalar_t<T_true, T_false>* = nullptr>
inline auto select(const T_bool c, const T_true y_true, const T_false y_false) {
return c.unaryExpr([&](bool cond) { return cond ? y_true : y_false; }).eval();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(optional) I personally prefer we just pull in the names of the things we want into a lambda instead of using &

Suggested change
return c.unaryExpr([&](bool cond) { return cond ? y_true : y_false; }).eval();
return c.unaryExpr([y_true, y_false](bool cond) { return cond ? y_true : y_false; }).eval();

Comment thread stan/math/prim/fun/select.hpp Outdated
Comment on lines +168 to +169
check_consistent_sizes("select", "boolean", c, "y_true", y_true, "y_false",
y_false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(optional) Should the names here be left hand side and right hand side? Like can this error be propagated up to users of Stan?

@andrjohns

Copy link
Copy Markdown
Collaborator Author

@SteveBronder this is ready for another look

@SteveBronder SteveBronder left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Mostly Qs on the signatures for containers and scalars as well as some docs.

Comment thread stan/math/prim/fun/all.hpp Outdated
Comment on lines +19 to +22
template <typename T, require_integral_t<T>* = nullptr>
constexpr inline T all(T x) {
return x;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We still want a bool return type right?

Suggested change
template <typename T, require_integral_t<T>* = nullptr>
constexpr inline T all(T x) {
return x;
}
template <typename T, require_integral_t<T>* = nullptr>
constexpr inline bool all(T x) {
return x;
}

Comment thread stan/math/prim/fun/all.hpp
Comment thread stan/math/prim/fun/select.hpp Outdated
Comment thread stan/math/prim/fun/select.hpp Outdated
*
* `select(c, y1, y0) = c ? y1 : y0`.
*
* @tparam T_true type of the true argument

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* @tparam T_true type of the true argument
* @tparam T_true A stan `Scalar` type

Generally for overloads try to have the type docs reflect the specific types for this overload

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please fix this for all the docs for the types

Comment thread stan/math/prim/fun/select.hpp Outdated
Comment on lines +32 to +33
* Return the second argument if the first argument is true
* and otherwise return the third argument. Eigen expressions are

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'd rephrase these slightly so it goes from left to right in terms of arguments

Suggested change
* Return the second argument if the first argument is true
* and otherwise return the third argument. Eigen expressions are
* If first argument is true return the second argument, else return the third argument

Comment thread stan/math/prim/fun/select.hpp
Comment on lines +150 to +153
return c
.unaryExpr(
[y_true, y_false](bool cond) { return cond ? y_true : y_false; })
.eval();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How is the return type decided here? I think we should do like the other functions and explicitly cast to our intended return type

Suggested change
return c
.unaryExpr(
[y_true, y_false](bool cond) { return cond ? y_true : y_false; })
.eval();
using ret_t = return_type_t<T_true, T_false>;
return c
.unaryExpr(
[y_true, y_false](bool cond) { return cond ? ret_t(y_true) : ret_t(y_false); })
.eval();

Comment thread stan/math/prim/fun/select.hpp Outdated
inline auto select(const T_bool c, const T_true y_true, const T_false y_false) {
check_consistent_sizes("select", "boolean", c, "left hand side", y_true,
"right hand side", y_false);
return c.select(y_true, y_false).eval();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same as before, I think we should be casting y_true and y_false to the same type before doing the select. idk how Eigen handles that under the hood and I'd rather just be explicit about it

@andrjohns

Copy link
Copy Markdown
Collaborator Author

@SteveBronder ready for another look

@SteveBronder SteveBronder left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Some very minor comments on the docs then I think this is good to go!

Comment thread stan/math/prim/fun/all.hpp Outdated
Comment on lines +66 to +67
* @tparam Type of container
* @param x Nested container of boolean inputs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

docs do not match signature

* Overload for a single boolean input
*
* @tparam T The type of integral input.
* @param x boolean input

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* @param x boolean input
* @param x integral input

Comment thread stan/math/prim/fun/any.hpp Outdated
*
* Overload for a single boolean input
*
* @tparam T The type of integral input.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* @tparam T The type of integral input.
* @tparam T Any type convertible to `bool`

@SteveBronder SteveBronder left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm!

@andrjohns
andrjohns merged commit 38289cd into stan-dev:develop Aug 11, 2023
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.

4 participants