From af0dc5b8815ce07dc89f1d88414f0791d045e98b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sat, 11 Jul 2026 19:39:41 +0800 Subject: [PATCH 1/5] Allow select to return eigen expressions if both branches match --- stan/math/prim/fun/select.hpp | 28 +++++++++++++++++++++++++ test/unit/math/prim/fun/select_test.cpp | 15 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/stan/math/prim/fun/select.hpp b/stan/math/prim/fun/select.hpp index a52e7e4954b..0a7c403d78d 100644 --- a/stan/math/prim/fun/select.hpp +++ b/stan/math/prim/fun/select.hpp @@ -50,6 +50,7 @@ template < typename T_true_plain = promote_scalar_t>, typename T_false_plain = promote_scalar_t>, require_all_container_t* = nullptr, + require_all_not_same_t* = nullptr, require_all_same_t* = nullptr> inline T_true_plain select(const bool c, T_true&& y_true, T_false&& y_false) { check_matching_dims("select", "left hand side", y_true, "right hand side", @@ -58,6 +59,33 @@ inline T_true_plain select(const bool c, T_true&& y_true, T_false&& y_false) { : T_true_plain(std::forward(y_false)); } +/** + * If first argument is true return the second argument, + * else return the third argument. Eigen expressions are + * evaluated so that the return type is the same for both branches. + * + * Both containers must have the same plain type. The scalar type + * of the return is determined by the return_type_t<> type trait. + * + * Overload for use with two containers. + * + * @tparam T_true A container of stan `Scalar` types + * @tparam T_false A container of stan `Scalar` types + * @param c Boolean condition value. + * @param y_true Value to return if condition is true. + * @param y_false Value to return if condition is false. + */ +template < + typename T_true, typename T_false, + require_all_container_t* = nullptr, + require_all_same_t* = nullptr> +inline auto select(const bool c, T_true&& y_true, T_false&& y_false) { + check_matching_dims("select", "left hand side", y_true, "right hand side", + y_false); + return c ? std::forward(y_true) + : std::forward(y_false); +} + /** * If first argument is true return the second argument, * else return the third argument. diff --git a/test/unit/math/prim/fun/select_test.cpp b/test/unit/math/prim/fun/select_test.cpp index 1086b27e9fc..e3d54bea581 100644 --- a/test/unit/math/prim/fun/select_test.cpp +++ b/test/unit/math/prim/fun/select_test.cpp @@ -202,3 +202,18 @@ TEST(MathFunctions, select_array_bool) { EXPECT_THROW(select(val < 0, b_array_short, b_array_short), std::invalid_argument); } + +TEST(MathFunction, select_same_expression) { + using stan::math::select; + using VecReplT = Eigen::Replicate; + using VecTransT = Eigen::Transpose; + + Eigen::VectorXd v(3); + v << 1.0, 4.0, 9.0; + + VecReplT repl_expr = stan::math::rep_matrix(v, 3); + VecTransT trans_expr = stan::math::transpose(v); + + VecReplT replicated_v = select(true, repl_expr, repl_expr); + VecTransT transp_v = select(false, trans_expr, trans_expr); +} From 61a690f610c11e4a8339d91fb17f2cf4022a8787 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sat, 11 Jul 2026 20:06:32 +0800 Subject: [PATCH 2/5] Condense overloads --- stan/math/prim/fun/select.hpp | 64 +++++++++++------------------------ 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/stan/math/prim/fun/select.hpp b/stan/math/prim/fun/select.hpp index 0a7c403d78d..eaa266a77c2 100644 --- a/stan/math/prim/fun/select.hpp +++ b/stan/math/prim/fun/select.hpp @@ -14,32 +14,32 @@ namespace math { * * `select(c, y1, y0) = c ? y1 : y0`. * - * @tparam T_true A stan `Scalar` type - * @tparam T_false A stan `Scalar` type + * @tparam ArgT Argument type, can be any type * @param c Boolean condition value. * @param y_true Value to return if condition is true. * @param y_false Value to return if condition is false. */ -template , - require_all_stan_scalar_t* = nullptr> -inline ReturnT select(const bool c, const T_true y_true, - const T_false y_false) { - return c ? ReturnT(y_true) : ReturnT(y_false); +template +inline auto select(const bool c, ArgT&& y_true, ArgT&& y_false) { + if constexpr (is_container_v) { + check_matching_dims("select", "left hand side", y_true, "right hand side", + y_false); + } + return c ? std::forward(y_true) : std::forward(y_false); } /** * If first argument is true return the second argument, - * else return the third argument. Eigen expressions are - * evaluated so that the return type is the same for both branches. + * else return the third argument. If the two branches have + * different types, but matching plain types (e.g., Eigen expressions), + * the return type is the plain type and the expression is evaluated. * - * Both containers must have the same plain type. The scalar type + * Both branches must have the same plain type. The scalar type * of the return is determined by the return_type_t<> type trait. * - * Overload for use with two containers. - * - * @tparam T_true A container of stan `Scalar` types - * @tparam T_false A container of stan `Scalar` types + * @tparam T_true Type of first argument, can be any type + * @tparam T_false Type of second argument, can be any type + * (with the same plain type as T_true) * @param c Boolean condition value. * @param y_true Value to return if condition is true. * @param y_false Value to return if condition is false. @@ -49,43 +49,17 @@ template < typename T_return = return_type_t, typename T_true_plain = promote_scalar_t>, typename T_false_plain = promote_scalar_t>, - require_all_container_t* = nullptr, require_all_not_same_t* = nullptr, require_all_same_t* = nullptr> inline T_true_plain select(const bool c, T_true&& y_true, T_false&& y_false) { - check_matching_dims("select", "left hand side", y_true, "right hand side", - y_false); + if constexpr (is_container_v) { + check_matching_dims("select", "left hand side", y_true, "right hand side", + y_false); + } return c ? T_true_plain(std::forward(y_true)) : T_true_plain(std::forward(y_false)); } -/** - * If first argument is true return the second argument, - * else return the third argument. Eigen expressions are - * evaluated so that the return type is the same for both branches. - * - * Both containers must have the same plain type. The scalar type - * of the return is determined by the return_type_t<> type trait. - * - * Overload for use with two containers. - * - * @tparam T_true A container of stan `Scalar` types - * @tparam T_false A container of stan `Scalar` types - * @param c Boolean condition value. - * @param y_true Value to return if condition is true. - * @param y_false Value to return if condition is false. - */ -template < - typename T_true, typename T_false, - require_all_container_t* = nullptr, - require_all_same_t* = nullptr> -inline auto select(const bool c, T_true&& y_true, T_false&& y_false) { - check_matching_dims("select", "left hand side", y_true, "right hand side", - y_false); - return c ? std::forward(y_true) - : std::forward(y_false); -} - /** * If first argument is true return the second argument, * else return the third argument. From 9f77a8072ca4e50ab4a968d9153ee7e216caee74 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sat, 11 Jul 2026 21:00:18 +0800 Subject: [PATCH 3/5] Fix opencl test --- stan/math/prim/fun/select.hpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/stan/math/prim/fun/select.hpp b/stan/math/prim/fun/select.hpp index eaa266a77c2..adddb714e44 100644 --- a/stan/math/prim/fun/select.hpp +++ b/stan/math/prim/fun/select.hpp @@ -14,18 +14,22 @@ namespace math { * * `select(c, y1, y0) = c ? y1 : y0`. * - * @tparam ArgT Argument type, can be any type + * @tparam T_true Type of first argument, can be any type + * @tparam T_false Type of second argument, can be any type + * convertible with the first * @param c Boolean condition value. * @param y_true Value to return if condition is true. * @param y_false Value to return if condition is false. */ -template -inline auto select(const bool c, ArgT&& y_true, ArgT&& y_false) { - if constexpr (is_container_v) { +template , + require_not_t>* = nullptr> +inline auto select(const bool c, T_true&& y_true, T_false&& y_false) { + if constexpr (is_container_v) { check_matching_dims("select", "left hand side", y_true, "right hand side", y_false); } - return c ? std::forward(y_true) : std::forward(y_false); + return c ? std::forward(y_true) : std::forward(y_false); } /** @@ -49,7 +53,7 @@ template < typename T_return = return_type_t, typename T_true_plain = promote_scalar_t>, typename T_false_plain = promote_scalar_t>, - require_all_not_same_t* = nullptr, + require_t>>* = nullptr, require_all_same_t* = nullptr> inline T_true_plain select(const bool c, T_true&& y_true, T_false&& y_false) { if constexpr (is_container_v) { From 5dead0ba724973a654eddd71b47b872e8a91a2da Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Sat, 11 Jul 2026 21:34:20 +0800 Subject: [PATCH 4/5] Update test case, fix conversion check --- stan/math/prim/fun/select.hpp | 21 +++++++++++++++++---- test/unit/math/prim/fun/select_test.cpp | 7 ++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/stan/math/prim/fun/select.hpp b/stan/math/prim/fun/select.hpp index adddb714e44..00aa6d98cc6 100644 --- a/stan/math/prim/fun/select.hpp +++ b/stan/math/prim/fun/select.hpp @@ -4,9 +4,19 @@ #include #include #include +#include namespace stan { namespace math { + namespace internal { + template + struct is_default_ternary : std::false_type {}; + + template + struct is_default_ternary() : std::declval()) + >> : std::true_type {}; + } /** * If first argument is true return the second argument, @@ -22,10 +32,9 @@ namespace math { * @param y_false Value to return if condition is false. */ template , - require_not_t>* = nullptr> + require_t>* = nullptr> inline auto select(const bool c, T_true&& y_true, T_false&& y_false) { - if constexpr (is_container_v) { + if constexpr (is_container_v || is_container_v) { check_matching_dims("select", "left hand side", y_true, "right hand side", y_false); } @@ -53,7 +62,11 @@ template < typename T_return = return_type_t, typename T_true_plain = promote_scalar_t>, typename T_false_plain = promote_scalar_t>, - require_t>>* = nullptr, + require_not_t>* = nullptr, + require_any_t< + conjunction, is_container>, + conjunction, is_stan_scalar> + >* = nullptr, require_all_same_t* = nullptr> inline T_true_plain select(const bool c, T_true&& y_true, T_false&& y_false) { if constexpr (is_container_v) { diff --git a/test/unit/math/prim/fun/select_test.cpp b/test/unit/math/prim/fun/select_test.cpp index e3d54bea581..19f1dc0dcae 100644 --- a/test/unit/math/prim/fun/select_test.cpp +++ b/test/unit/math/prim/fun/select_test.cpp @@ -205,13 +205,18 @@ TEST(MathFunctions, select_array_bool) { TEST(MathFunction, select_same_expression) { using stan::math::select; + using stan::math::rep_matrix; using VecReplT = Eigen::Replicate; using VecTransT = Eigen::Transpose; Eigen::VectorXd v(3); v << 1.0, 4.0, 9.0; + Eigen::RowVectorXd rv(3); + rv << 1.0, 4.0, 9.0; - VecReplT repl_expr = stan::math::rep_matrix(v, 3); + Eigen::MatrixXd rtn = select(true, rep_matrix(v, 3), rep_matrix(rv, 3)); + + VecReplT repl_expr = rep_matrix(v, 3); VecTransT trans_expr = stan::math::transpose(v); VecReplT replicated_v = select(true, repl_expr, repl_expr); From 58af029ca46d1ca6d691f9efd3f0480980422df5 Mon Sep 17 00:00:00 2001 From: Stan Jenkins Date: Sat, 11 Jul 2026 09:35:26 -0400 Subject: [PATCH 5/5] [Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1 --- stan/math/prim/fun/select.hpp | 24 ++++++++++++------------ test/unit/math/prim/fun/select_test.cpp | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/stan/math/prim/fun/select.hpp b/stan/math/prim/fun/select.hpp index 00aa6d98cc6..f5ea8814d5c 100644 --- a/stan/math/prim/fun/select.hpp +++ b/stan/math/prim/fun/select.hpp @@ -8,15 +8,16 @@ namespace stan { namespace math { - namespace internal { - template - struct is_default_ternary : std::false_type {}; +namespace internal { +template +struct is_default_ternary : std::false_type {}; - template - struct is_default_ternary() : std::declval()) - >> : std::true_type {}; - } +template +struct is_default_ternary() + : std::declval())>> + : std::true_type {}; +} // namespace internal /** * If first argument is true return the second argument, @@ -63,10 +64,9 @@ template < typename T_true_plain = promote_scalar_t>, typename T_false_plain = promote_scalar_t>, require_not_t>* = nullptr, - require_any_t< - conjunction, is_container>, - conjunction, is_stan_scalar> - >* = nullptr, + require_any_t, is_container>, + conjunction, + is_stan_scalar>>* = nullptr, require_all_same_t* = nullptr> inline T_true_plain select(const bool c, T_true&& y_true, T_false&& y_false) { if constexpr (is_container_v) { diff --git a/test/unit/math/prim/fun/select_test.cpp b/test/unit/math/prim/fun/select_test.cpp index 19f1dc0dcae..13014c847ca 100644 --- a/test/unit/math/prim/fun/select_test.cpp +++ b/test/unit/math/prim/fun/select_test.cpp @@ -204,8 +204,8 @@ TEST(MathFunctions, select_array_bool) { } TEST(MathFunction, select_same_expression) { - using stan::math::select; using stan::math::rep_matrix; + using stan::math::select; using VecReplT = Eigen::Replicate; using VecTransT = Eigen::Transpose;