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

adds testing suite for nested binary var matrix functions #2502

Merged
merged 15 commits into from
Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion stan/math/prim/fun/as_array_or_scalar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,33 @@ inline auto as_array_or_scalar(T&& v) {
* @param v Specified vector.
* @return Matrix converted to an array.
*/
template <typename T, require_std_vector_t<T>* = nullptr>
template <typename T, require_std_vector_t<T>* = nullptr,
require_not_std_vector_t<value_type_t<T>>* = nullptr>
inline auto as_array_or_scalar(T&& v) {
using T_map
= Eigen::Map<const Eigen::Array<value_type_t<T>, Eigen::Dynamic, 1>>;
return make_holder([](auto& x) { return T_map(x.data(), x.size()); },
std::forward<T>(v));
}

/**
* Converts an std::vector<std::vector> to an Eigen Array.
* @tparam T A standard vector with inner container of a standard vector
* with an inner stan scalar.
* @param v specified vector of vectorised
* @return An Eigen Array with dynamic rows and columns.
*/
template <typename T, require_std_vector_vt<is_std_vector, T>* = nullptr,
require_std_vector_vt<is_stan_scalar, value_type_t<T>>* = nullptr>
inline auto as_array_or_scalar(T&& v) {
Eigen::Array<scalar_type_t<T>, -1, -1> ret(v.size(), v[0].size());
for (size_t i = 0; i < v.size(); ++i) {
ret.row(i) = Eigen::Map<const Eigen::Array<scalar_type_t<T>, 1, -1>>(
v[i].data(), v[i].size());
}
return ret;
}

} // namespace math
} // namespace stan

Expand Down
2 changes: 1 addition & 1 deletion stan/math/prim/fun/beta.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ template <typename T1, typename T2, require_any_container_t<T1, T2>* = nullptr,
require_all_not_var_matrix_t<T1, T2>* = nullptr>
inline auto beta(const T1& a, const T2& b) {
return apply_scalar_binary(
a, b, [&](const auto& c, const auto& d) { return beta(c, d); });
a, b, [](const auto& c, const auto& d) { return beta(c, d); });
}

} // namespace math
Expand Down
4 changes: 4 additions & 0 deletions stan/math/rev/fun/bessel_first_kind.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ inline var bessel_first_kind(int v, const var& a) {
});
}

/**
* Overload with `var_value<Matrix>` for `int`, `std::vector<int>`, and
* `std::vector<std::vector<int>>`
*/
template <typename T1, typename T2, require_st_integral<T1>* = nullptr,
require_eigen_t<T2>* = nullptr>
inline auto bessel_first_kind(const T1& v, const var_value<T2>& a) {
Expand Down
4 changes: 4 additions & 0 deletions stan/math/rev/fun/bessel_second_kind.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ inline var bessel_second_kind(int v, const var& a) {
});
}

/**
* Overload with `var_value<Matrix>` for `int`, `std::vector<int>`, and
* `std::vector<std::vector<int>>`
*/
template <typename T1, typename T2, require_st_integral<T1>* = nullptr,
require_eigen_t<T2>* = nullptr>
inline auto bessel_second_kind(const T1& v, const var_value<T2>& a) {
Expand Down
17 changes: 11 additions & 6 deletions stan/math/rev/fun/binary_log_loss.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ inline var binary_log_loss(int y, const var& y_hat) {
}
}

/**
* Overload with `int` and `var_value<Matrix>`
*/
template <typename Mat, require_eigen_t<Mat>* = nullptr>
inline auto binary_log_loss(int y, const var_value<Mat>& y_hat) {
if (y == 0) {
Expand All @@ -70,12 +73,14 @@ inline auto binary_log_loss(int y, const var_value<Mat>& y_hat) {
}
}

template <typename Mat, require_eigen_t<Mat>* = nullptr>
inline auto binary_log_loss(const std::vector<int>& y,
const var_value<Mat>& y_hat) {
arena_t<Eigen::Array<bool, -1, 1>> arena_y
= Eigen::Map<const Eigen::Array<int, -1, 1>>(y.data(), y.size())
.cast<bool>();
/**
* Overload with `var_value<Matrix>` for `std::vector<int>` and
* `std::vector<std::vector<int>>`
*/
template <typename StdVec, typename Mat, require_eigen_t<Mat>* = nullptr,
require_st_integral<StdVec>* = nullptr>
inline auto binary_log_loss(const StdVec& y, const var_value<Mat>& y_hat) {
auto arena_y = to_arena(as_array_or_scalar(y).template cast<bool>());
auto ret_val
= -(arena_y == 0)
.select((-y_hat.val().array()).log1p(), y_hat.val().array().log());
Expand Down
1 change: 1 addition & 0 deletions stan/math/rev/functor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <stan/math/rev/functor/algebra_solver_newton.hpp>
#include <stan/math/rev/functor/algebra_system.hpp>
#include <stan/math/rev/functor/apply_scalar_unary.hpp>
#include <stan/math/rev/functor/apply_scalar_binary.hpp>
#include <stan/math/rev/functor/apply_vector_unary.hpp>
#include <stan/math/rev/functor/coupled_ode_system.hpp>
#include <stan/math/rev/functor/cvodes_integrator.hpp>
Expand Down
248 changes: 248 additions & 0 deletions stan/math/rev/functor/apply_scalar_binary.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
#ifndef STAN_MATH_REV_FUNCTOR_APPLY_SCALAR_BINARY_HPP
#define STAN_MATH_REV_FUNCTOR_APPLY_SCALAR_BINARY_HPP

#include <stan/math/prim/fun/as_column_vector_or_scalar.hpp>
#include <stan/math/rev/meta.hpp>
#include <stan/math/prim/err/check_matching_dims.hpp>
#include <stan/math/prim/err/check_matching_sizes.hpp>
#include <stan/math/prim/fun/num_elements.hpp>
#include <vector>

namespace stan {
namespace math {

/**
* Specialisation for use with combinations of
* `Eigen::Matrix` and `var_value<Eigen::Matrix>` inputs.
* Eigen's binaryExpr framework is used for more efficient indexing of both row-
* and column-major inputs without separate loops.
andrjohns marked this conversation as resolved.
Show resolved Hide resolved
*
* @tparam T1 Type of first argument to which functor is applied.
* @tparam T2 Type of second argument to which functor is applied.
* @tparam F Type of functor to apply.
* @param x First Matrix input to which operation is applied.
* @param y Second Matrix input to which operation is applied.
* @param f functor to apply to Matrix inputs.
* @return `var_value<Matrix>` with result of applying functor to inputs.
*/
template <typename T1, typename T2, typename F,
require_any_var_matrix_t<T1, T2>* = nullptr,
require_all_matrix_t<T1, T2>* = nullptr>
andrjohns marked this conversation as resolved.
Show resolved Hide resolved
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
check_matching_dims("Binary function", "x", x, "y", y);
return f(x, y);
}

/**
* Specialisation for use with one `var_value<Eigen vector>` (row or column) and
* a one-dimensional std::vector of integer types
*
* @tparam T1 Type of first argument to which functor is applied.
* @tparam T2 Type of second argument to which functor is applied.
* @tparam F Type of functor to apply.
* @param x Matrix input to which operation is applied.
* @param y Integer std::vector input to which operation is applied.
* @param f functor to apply to inputs.
* @return var_value<Eigen> object with result of applying functor to inputs.
*/
template <typename T1, typename T2, typename F,
require_var_matrix_t<T1>* = nullptr,
require_std_vector_vt<std::is_integral, T2>* = nullptr>
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
check_matching_sizes("Binary function", "x", x, "y", y);
return f(x, y);
}

/**
* Specialisation for use with a one-dimensional std::vector of integer types
* and one `var_value<Eigen vector>` (row or column).
*
* @tparam T1 Type of first argument to which functor is applied.
* @tparam T2 Type of second argument to which functor is applied.
* @tparam F Type of functor to apply.
* @param x Integer std::vector input to which operation is applied.
* @param y Eigen input to which operation is applied.
* @param f functor to apply to inputs.
* @return Eigen object with result of applying functor to inputs.
*/
template <typename T1, typename T2, typename F,
require_std_vector_vt<std::is_integral, T1>* = nullptr,
require_var_matrix_t<T2>* = nullptr>
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
check_matching_sizes("Binary function", "x", x, "y", y);
return f(x, y);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
template <typename T1, typename T2, typename F,
require_std_vector_vt<std::is_integral, T1>* = nullptr,
require_var_matrix_t<T2>* = nullptr>
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
check_matching_sizes("Binary function", "x", x, "y", y);
return f(x, y);
}
template <typename T1, typename T2, typename F,
require_any_std_vector_vt<std::is_integral, T1, T2>* = nullptr,
require_any_var_matrix_t<T1, T2>* = nullptr>
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
check_matching_sizes("Binary function", "x", x, "y", y);
return f(x, y);
}

Then you don't need the separate overloads


/**
* Specialisation for use with one `var_value<Matrix>` and
* a two-dimensional std::vector of integer types
*
* @tparam T1 Type of first argument to which functor is applied.
* @tparam T2 Type of second argument to which functor is applied.
* @tparam F Type of functor to apply.
* @param x var with Eigen matrix inner type to which operation is applied.
* @param y Nested integer std::vector input to which operation is applied.
* @param f functor to apply to inputs.
* @return Eigen object with result of applying functor to inputs.
*/
template <typename T1, typename T2, typename F,
require_var_matrix_t<T1>* = nullptr,
require_std_vector_vt<is_std_vector, T2>* = nullptr,
require_std_vector_st<std::is_integral, T2>* = nullptr>
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
return f(x, y);
}

/**
* Specialisation for use with a two-dimensional std::vector of integer types
* and one `var_value<Matrix>`.
*
* @tparam T1 Type of first argument to which functor is applied.
* @tparam T2 Type of second argument to which functor is applied.
* @tparam F Type of functor to apply.
* @param x Nested integer std::vector input to which operation is applied.
* @param y var value with inner Eigen matrix input to which operation is
* applied.
* @param f functor to apply to inputs.
* @return Eigen object with result of applying functor to inputs.
*/
template <typename T1, typename T2, typename F,
require_std_vector_vt<is_std_vector, T1>* = nullptr,
require_std_vector_st<std::is_integral, T1>* = nullptr,
require_var_matrix_t<T2>* = nullptr>
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
return f(x, y);
}
andrjohns marked this conversation as resolved.
Show resolved Hide resolved

/**
* Specialisation for use when the first input is an `var_value<Eigen> type and
* the second is a scalar.
*
* @tparam T1 Type of `var_value<Matrix>` object to which functor is applied.
* @tparam T2 Type of scalar to which functor is applied.
* @tparam F Type of functor to apply.
* @param x Matrix input to which operation is applied.
* @param y Scalar input to which operation is applied.
* @param f functor to apply to var matrix and scalar inputs.
* @return `var_value<Matrix> object with result of applying functor to inputs.
*
* Note: The return expresssion needs to be evaluated, otherwise the captured
* function and scalar fall out of scope.
*/
template <typename T1, typename T2, typename F,
require_var_matrix_t<T1>* = nullptr,
require_stan_scalar_t<T2>* = nullptr>
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
return f(x, y);
}

/**
* Specialisation for use when the first input is an scalar and the second is
* an `var_value<Eigen>`.
*
* @tparam T1 Type of scalar to which functor is applied.
* @tparam T2 var value with inner Eigen type to which functor is applied.
* @tparam F Type of functor to apply.
* @param x Scalar input to which operation is applied.
* @param y var matrix input to which operation is applied.
* @param f functor to apply to Eigen and scalar inputs.
* @return var value with inner Eigen type with result of applying functor to
* inputs.
*
* Note: The return expresssion needs to be evaluated, otherwise the captured
* function and scalar fall out of scope.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can also cut this comment since unaryExpr doesn't get called for varmat types

template <typename T1, typename T2, typename F,
require_stan_scalar_t<T1>* = nullptr,
require_var_matrix_t<T2>* = nullptr>
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
return f(x, y);
}

/**
* Specialisation for use when the first input is a nested std::vector and the
* second is a scalar. The returned scalar type is deduced to allow for cases
* where the input and return scalar types differ (e.g., functions implicitly
* promoting integers).
*
* @tparam T1 Type of std::vector to which functor is applied.
* @tparam T2 Type of scalar to which functor is applied.
* @tparam F Type of functor to apply.
* @param x std::vector input to which operation is applied.
* @param y Scalar input to which operation is applied.
* @param f functor to apply to inputs.
* @return std::vector with result of applying functor to inputs.
*/
template <typename T1, typename T2, typename F,
andrjohns marked this conversation as resolved.
Show resolved Hide resolved
require_std_vector_t<T1>* = nullptr,
require_var_matrix_t<value_type_t<T1>>* = nullptr,
require_stan_scalar_t<T2>* = nullptr>
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
using T_return = plain_type_t<decltype(apply_scalar_binary(x[0], y, f))>;
size_t x_size = x.size();
std::vector<T_return> result(x_size);
for (size_t i = 0; i < x_size; ++i) {
result[i] = apply_scalar_binary(x[i], y, f);
}
return result;
}

/**
* Specialisation for use when the first input is a scalar and the second is a
* nested std::vector. The returned scalar type is deduced to allow for cases
* where the input and return scalar types differ (e.g., functions implicitly
* promoting integers).
*
* @tparam T1 Type of scalar to which functor is applied.
* @tparam T2 Type of std::vector to which functor is applied.
* @tparam F Type of functor to apply.
* @param x Scalar input to which operation is applied.
* @param y std::vector input to which operation is applied.
* @param f functor to apply to inputs.
* @return std::vector with result of applying functor to inputs.
*/
template <typename T1, typename T2, typename F,
require_stan_scalar_t<T1>* = nullptr,
require_std_vector_t<T2>* = nullptr,
require_var_matrix_t<value_type_t<T2>>* = nullptr>
inline auto apply_scalar_binary(const T1& x, const T2& y, const F& f) {
using T_return = plain_type_t<decltype(apply_scalar_binary(x, y[0], f))>;
size_t y_size = y.size();
std::vector<T_return> result(y_size);
for (size_t i = 0; i < y_size; ++i) {
result[i] = apply_scalar_binary(x, y[i], f);
}
return result;
}

/**
* Specialisation for use with two nested containers (std::vectors).
* The returned scalar type is deduced to allow for cases where the input and
* return scalar types differ (e.g., functions implicitly promoting
* integers).
*
* @tparam T1 Type of first std::vector to which functor is applied.
* @tparam T2 Type of second std::vector to which functor is applied.
* @tparam F Type of functor to apply.
* @param x First std::vector input to which operation is applied.
* @param y Second std::vector input to which operation is applied.
* @param f functor to apply to std::vector inputs.
* @return std::vector with result of applying functor to inputs.
*/
template <typename T1, typename T2, typename F,
require_any_var_matrix_t<T1, T2>* = nullptr>
inline auto apply_scalar_binary(const std::vector<T1>& x,
const std::vector<T2>& y, const F& f) {
check_matching_sizes("Binary function", "x", x, "y", y);
using T_return = plain_type_t<decltype(apply_scalar_binary(x[0], y[0], f))>;
size_t y_size = y.size();
std::vector<T_return> result(y_size);
for (size_t i = 0; i < y_size; ++i) {
result[i] = apply_scalar_binary(x[i], y[i], f);
}
return result;
}

} // namespace math
} // namespace stan
#endif
8 changes: 3 additions & 5 deletions test/unit/math/mix/fun/bessel_first_kind_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ TEST(mathMixScalFun, besselFirstKind_matvec) {
};

std::vector<int> std_in1{3, 1};
Eigen::VectorXd in2(2);
in2 << 0.5, 3.4;
stan::test::expect_ad_matvar(f, std_in1, in2);

stan::test::expect_ad_matvar(f, std_in1[0], in2);
Eigen::MatrixXd in2(2, 2);
in2 << 0.5, 3.4, 0.5, 3.4;
stan::test::expect_ad_vectorized_matvar(f, std_in1, in2);
}
7 changes: 3 additions & 4 deletions test/unit/math/mix/fun/bessel_second_kind_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ TEST(mathMixScalFun, besselSecondKind_matvec) {
};

std::vector<int> std_in1{3, 1};
Eigen::VectorXd in2(2);
in2 << 0.5, 3.4;
stan::test::expect_ad_matvar(f, std_in1, in2);
stan::test::expect_ad_matvar(f, std_in1[0], in2);
Eigen::MatrixXd in2(2, 2);
in2 << 0.5, 3.4, 0.5, 3.4;
stan::test::expect_ad_vectorized_matvar(f, std_in1, in2);
}
14 changes: 14 additions & 0 deletions test/unit/math/mix/fun/beta2_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <test/unit/math/test_ad.hpp>
andrjohns marked this conversation as resolved.
Show resolved Hide resolved

TEST(mathMixScalFun, beta_varmat_vectorized) {
auto f = [](const auto& x1, const auto& x2) {
using stan::math::beta;
return beta(x1, x2);
};

Eigen::MatrixXd in1(2, 2);
in1 << 0.5, 3.4, 5.2, 0.5;
Eigen::MatrixXd in2(2, 2);
in2 << 3.3, 0.9, 6.7, 3.3;
stan::test::expect_ad_vectorized_matvar(f, in1, in2);
}
Loading