Skip to content

Commit fe4800a

Browse files
authored
Merge pull request #3376 from SteveBronder/test/reduce-sum-tuple-regressions-3041-3359
Allow `reduce_sum` to take in tuples and tuples of tuples
2 parents 344d716 + 119a1cf commit fe4800a

10 files changed

Lines changed: 295 additions & 12 deletions

stan/math/rev/core/accumulate_adjoints.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef STAN_MATH_REV_CORE_ACCUMULATE_ADJOINTS_HPP
22
#define STAN_MATH_REV_CORE_ACCUMULATE_ADJOINTS_HPP
33

4+
#include <stan/math/prim/functor/apply.hpp>
45
#include <stan/math/prim/meta.hpp>
56
#include <stan/math/rev/meta.hpp>
67
#include <stan/math/rev/core/var.hpp>
@@ -33,6 +34,9 @@ template <typename Arith, require_st_arithmetic<Arith>* = nullptr,
3334
typename... Pargs>
3435
inline double* accumulate_adjoints(double* dest, Arith&& x, Pargs&&... args);
3536

37+
template <typename Tuple, require_tuple_t<Tuple>* = nullptr, typename... Pargs>
38+
inline double* accumulate_adjoints(double* dest, Tuple&& x, Pargs&&... args);
39+
3640
inline double* accumulate_adjoints(double* dest);
3741

3842
/**
@@ -140,6 +144,27 @@ inline double* accumulate_adjoints(double* dest, Arith&& x, Pargs&&... args) {
140144
return accumulate_adjoints(dest, std::forward<Pargs>(args)...);
141145
}
142146

147+
/**
148+
* Accumulate adjoints from a tuple into storage pointed to by dest, then
149+
* recursively accumulate adjoints from the remaining arguments.
150+
*
151+
* @tparam Tuple A tuple type
152+
* @tparam Pargs Types of remaining arguments
153+
* @param dest Pointer to where adjoints are to be accumulated
154+
* @param x A tuple containing arguments whose adjoints are accumulated
155+
* @param args Further args to accumulate over
156+
* @return Final position of adjoint storage pointer
157+
*/
158+
template <typename Tuple, require_tuple_t<Tuple>*, typename... Pargs>
159+
inline double* accumulate_adjoints(double* dest, Tuple&& x, Pargs&&... args) {
160+
dest = stan::math::apply(
161+
[dest](auto&&... tuple_args) {
162+
return accumulate_adjoints(dest, tuple_args...);
163+
},
164+
std::forward<Tuple>(x));
165+
return accumulate_adjoints(dest, std::forward<Pargs>(args)...);
166+
}
167+
143168
/**
144169
* End accumulate_adjoints recursion and return pointer
145170
*

stan/math/rev/core/count_vars.hpp

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ template <typename Arith, require_arithmetic_t<scalar_type_t<Arith>>* = nullptr,
3333
typename... Pargs>
3434
inline size_t count_vars_impl(size_t count, Arith& x, Pargs&&... args);
3535

36+
template <typename Tuple, require_tuple_t<Tuple>* = nullptr, typename... Pargs>
37+
inline size_t count_vars_impl(size_t count, Tuple&& x, Pargs&&... args);
38+
3639
inline size_t count_vars_impl(size_t count);
3740
/**
3841
* Count the number of vars in x (a std::vector of vars),
@@ -132,23 +135,33 @@ inline size_t count_vars_impl(size_t count, Arith& x, Pargs&&... args) {
132135
inline size_t count_vars_impl(size_t count, std::basic_ostream<char>*&) {
133136
return count;
134137
}
138+
139+
/**
140+
* Count the vars in a tuple, add them to the running total, and count the vars
141+
* in the remaining arguments.
142+
*
143+
* @tparam Tuple A tuple type
144+
* @tparam Pargs Types of remaining arguments
145+
* @param[in] count The current count of the number of vars
146+
* @param[in] x A tuple containing arguments to count
147+
* @param[in] args Objects to be forwarded to the recursive call
148+
* @return The total number of vars
149+
*/
150+
template <typename Tuple, require_tuple_t<Tuple>*, typename... Pargs>
151+
inline size_t count_vars_impl(size_t count, Tuple&& x, Pargs&&... args) {
152+
count = stan::math::apply(
153+
[count](auto&&... tuple_args) {
154+
return count_vars_impl(count, tuple_args...);
155+
},
156+
std::forward<Tuple>(x));
157+
return count_vars_impl(count, std::forward<Pargs>(args)...);
158+
}
159+
135160
/**
136161
* End count_vars_impl recursion and return total number of counted vars
137162
*/
138163
inline size_t count_vars_impl(size_t count) { return count; }
139164

140-
template <typename... Pargs, typename... Args>
141-
inline size_t count_vars_impl(std::size_t count,
142-
const std::tuple<Pargs...>& arg, Args&&... args) {
143-
return count_vars_impl(
144-
stan::math::apply(
145-
[count](auto&&... inner_args) {
146-
return (count_vars_impl(0, inner_args) + ... + count);
147-
},
148-
arg),
149-
std::forward<Args>(args)...);
150-
}
151-
152165
} // namespace internal
153166

154167
/**

stan/math/rev/core/deep_copy_vars.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef STAN_MATH_REV_CORE_DEEP_COPY_VARS_HPP
22
#define STAN_MATH_REV_CORE_DEEP_COPY_VARS_HPP
33

4+
#include <stan/math/prim/functor/apply.hpp>
5+
#include <stan/math/prim/functor/make_holder_tuple.hpp>
46
#include <stan/math/prim/meta.hpp>
57
#include <stan/math/rev/meta.hpp>
68
#include <stan/math/rev/core/var.hpp>
@@ -81,6 +83,23 @@ inline auto deep_copy_vars(EigT&& arg) {
8183
.eval();
8284
}
8385

86+
/**
87+
* Copy the vars in a tuple but reallocate new varis for them.
88+
*
89+
* @tparam Tuple A tuple type
90+
* @param arg A tuple containing arguments to copy
91+
* @return A tuple containing copied arguments
92+
*/
93+
template <typename Tuple, require_tuple_t<Tuple>* = nullptr>
94+
inline auto deep_copy_vars(Tuple&& arg) {
95+
return stan::math::apply(
96+
[](auto&&... tuple_args) {
97+
return make_holder_tuple(
98+
deep_copy_vars(std::forward<decltype(tuple_args)>(tuple_args))...);
99+
},
100+
std::forward<Tuple>(arg));
101+
}
102+
84103
} // namespace math
85104
} // namespace stan
86105

stan/math/rev/core/save_varis.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define STAN_MATH_REV_CORE_SAVE_VARIS_HPP
33

44
#include <stan/math/prim/fun/Eigen.hpp>
5+
#include <stan/math/prim/functor/apply.hpp>
56
#include <stan/math/prim/meta.hpp>
67
#include <stan/math/rev/meta.hpp>
78
#include <stan/math/rev/core/var.hpp>
@@ -33,6 +34,9 @@ template <typename Arith, require_st_arithmetic<Arith>* = nullptr,
3334
typename... Pargs>
3435
inline vari** save_varis(vari** dest, Arith&& x, Pargs&&... args);
3536

37+
template <typename Tuple, require_tuple_t<Tuple>* = nullptr, typename... Pargs>
38+
inline vari** save_varis(vari** dest, Tuple&& x, Pargs&&... args);
39+
3640
inline vari** save_varis(vari** dest);
3741

3842
/**
@@ -136,6 +140,25 @@ inline vari** save_varis(vari** dest, Arith&& x, Pargs&&... args) {
136140
return save_varis(dest, std::forward<Pargs>(args)...);
137141
}
138142

143+
/**
144+
* Save the vari pointers in a tuple into the memory pointed to by dest, then
145+
* recursively save the varis in the remaining arguments.
146+
*
147+
* @tparam Tuple A tuple type
148+
* @tparam Pargs Types of remaining arguments
149+
* @param[in, out] dest Pointer to where vari pointers are saved
150+
* @param[in] x A tuple containing arguments whose varis are saved
151+
* @param[in] args Additional arguments to have their varis saved
152+
* @return Final position of dest pointer
153+
*/
154+
template <typename Tuple, require_tuple_t<Tuple>*, typename... Pargs>
155+
inline vari** save_varis(vari** dest, Tuple&& x, Pargs&&... args) {
156+
dest = stan::math::apply(
157+
[dest](auto&&... tuple_args) { return save_varis(dest, tuple_args...); },
158+
std::forward<Tuple>(x));
159+
return save_varis(dest, std::forward<Pargs>(args)...);
160+
}
161+
139162
/**
140163
* End save_varis recursion and return pointer
141164
*

test/unit/math/rev/core/accumulate_adjoints_test.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <stan/math/rev/core.hpp>
33
#include <stan/math.hpp>
44
#include <test/unit/math/rev/util.hpp>
5+
#include <tuple>
56
#include <vector>
67

78
TEST_F(AgradRev, Rev_accumulate_adjoints_zero_args) {
@@ -437,3 +438,39 @@ TEST_F(AgradRev, Rev_accumulate_adjoints_sum) {
437438
EXPECT_EQ(ptr, storage.data() + num_vars);
438439
stan::math::recover_memory();
439440
}
441+
442+
TEST_F(AgradRev, Rev_accumulate_adjoints_tuple_args) {
443+
const std::tuple<> empty;
444+
const auto data = std::make_tuple(1, Eigen::VectorXd::Ones(2));
445+
Eigen::VectorXd data_storage = Eigen::VectorXd::Zero(2);
446+
double* data_ptr
447+
= stan::math::accumulate_adjoints(data_storage.data(), empty, data);
448+
EXPECT_EQ(data_storage.data(), data_ptr);
449+
EXPECT_FLOAT_EQ(0.0, data_storage(0));
450+
EXPECT_FLOAT_EQ(0.0, data_storage(1));
451+
452+
stan::math::var before = 1.0;
453+
stan::math::var first = 2.0;
454+
Eigen::Matrix<stan::math::var, Eigen::Dynamic, 1> vars(2);
455+
vars << 3.0, 4.0;
456+
stan::math::var last = 5.0;
457+
stan::math::var after = 6.0;
458+
before.vi_->adj_ = 1.0;
459+
first.vi_->adj_ = 2.0;
460+
vars(0).vi_->adj_ = 3.0;
461+
vars(1).vi_->adj_ = 4.0;
462+
last.vi_->adj_ = 5.0;
463+
after.vi_->adj_ = 6.0;
464+
auto nested = std::make_tuple(first, std::make_tuple(vars, 7), last);
465+
Eigen::VectorXd storage = Eigen::VectorXd::Constant(8, 10.0);
466+
467+
double* ptr = stan::math::accumulate_adjoints(storage.data(), before, nested,
468+
std::make_tuple(after));
469+
470+
EXPECT_EQ(storage.data() + 6, ptr);
471+
for (int i = 0; i < 6; ++i) {
472+
EXPECT_FLOAT_EQ(11.0 + i, storage(i));
473+
}
474+
EXPECT_FLOAT_EQ(10.0, storage(6));
475+
EXPECT_FLOAT_EQ(10.0, storage(7));
476+
}

test/unit/math/rev/core/count_vars_test.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <stan/math.hpp>
44
#include <test/unit/math/rev/util.hpp>
55

6+
#include <tuple>
67
#include <vector>
78

89
using stan::math::var;
@@ -161,3 +162,18 @@ TEST_F(AgradRev, Rev_count_vars_sum) {
161162
count_vars(arg1, arg18, arg17, arg2, arg16, arg3, arg15, arg4, arg14,
162163
arg5, arg13, arg12, arg6, arg11, arg7, arg10, arg8, arg9));
163164
}
165+
166+
TEST_F(AgradRev, Rev_count_vars_tuple_args) {
167+
const std::tuple<> empty;
168+
const auto data = std::make_tuple(1, Eigen::VectorXd::Ones(2));
169+
EXPECT_EQ(0, stan::math::count_vars(empty));
170+
EXPECT_EQ(0, stan::math::count_vars(data));
171+
172+
Eigen::Matrix<var, Eigen::Dynamic, 1> vars(2);
173+
auto nested = std::make_tuple(var(1.0), std::make_tuple(vars, 2.0), var(3.0));
174+
var before = 4.0;
175+
176+
EXPECT_EQ(5, stan::math::count_vars(before, nested));
177+
EXPECT_EQ(2, stan::math::count_vars(
178+
std::make_tuple(var(5.0), std::make_tuple(var(6.0)))));
179+
}

test/unit/math/rev/core/deep_copy_vars_test.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#include <stan/math/rev/core.hpp>
33
#include <stan/math.hpp>
44
#include <test/unit/math/rev/util.hpp>
5+
#include <tuple>
6+
#include <type_traits>
57
#include <vector>
68

79
using stan::math::var;
@@ -306,3 +308,45 @@ TEST_F(AgradRev, Rev_deep_copy_vars_std_vector_eigen_matrix_var_arg) {
306308
EXPECT_NE(out[i](j).vi_, arg[i](j).vi_);
307309
}
308310
}
311+
312+
TEST_F(AgradRev, Rev_deep_copy_vars_tuple_data_arg) {
313+
const std::tuple<> empty;
314+
const auto arg = std::make_tuple(5, Eigen::VectorXd::Ones(2).eval());
315+
316+
auto empty_out = stan::math::deep_copy_vars(empty);
317+
auto out = stan::math::deep_copy_vars(arg);
318+
319+
static_assert(std::is_same_v<decltype(empty_out), std::tuple<>>);
320+
static_assert(std::is_same_v<decltype(out),
321+
std::tuple<const int&, const Eigen::VectorXd&>>);
322+
EXPECT_EQ(&std::get<0>(out), &std::get<0>(arg));
323+
EXPECT_EQ(&std::get<1>(out), &std::get<1>(arg));
324+
}
325+
326+
TEST_F(AgradRev, Rev_deep_copy_vars_nested_tuple_var_arg) {
327+
Eigen::Matrix<var, Eigen::Dynamic, 1> vars(2);
328+
vars << 2.0, 3.0;
329+
auto arg = std::make_tuple(1.0, var(4.0), vars, std::make_tuple(var(5.0), 6));
330+
331+
auto out = stan::math::deep_copy_vars(arg);
332+
333+
static_assert(std::is_reference_v<std::tuple_element_t<0, decltype(out)>>);
334+
EXPECT_EQ(&std::get<0>(out), &std::get<0>(arg));
335+
EXPECT_FLOAT_EQ(std::get<1>(out).val(), std::get<1>(arg).val());
336+
EXPECT_NE(std::get<1>(out).vi_, std::get<1>(arg).vi_);
337+
for (int i = 0; i < vars.size(); ++i) {
338+
EXPECT_FLOAT_EQ(std::get<2>(out)(i).val(), std::get<2>(arg)(i).val());
339+
EXPECT_NE(std::get<2>(out)(i).vi_, std::get<2>(arg)(i).vi_);
340+
}
341+
EXPECT_FLOAT_EQ(std::get<0>(std::get<3>(out)).val(),
342+
std::get<0>(std::get<3>(arg)).val());
343+
EXPECT_NE(std::get<0>(std::get<3>(out)).vi_,
344+
std::get<0>(std::get<3>(arg)).vi_);
345+
EXPECT_EQ(&std::get<1>(std::get<3>(out)), &std::get<1>(std::get<3>(arg)));
346+
347+
auto rvalue_out = stan::math::deep_copy_vars(
348+
std::make_tuple(7.0, var(8.0), std::make_tuple(9)));
349+
static_assert(std::is_same_v<decltype(rvalue_out),
350+
std::tuple<double, var, std::tuple<int>>>);
351+
EXPECT_FLOAT_EQ(8.0, std::get<1>(rvalue_out).val());
352+
}

test/unit/math/rev/core/save_varis_test.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <stan/math.hpp>
22
#include <test/unit/math/rev/util.hpp>
33
#include <gtest/gtest.h>
4+
#include <tuple>
45
#include <vector>
56

67
using stan::math::var;
@@ -424,3 +425,34 @@ TEST_F(AgradRev, Rev_save_varis_sum) {
424425

425426
EXPECT_EQ(ptr, storage.data() + num_vars);
426427
}
428+
429+
TEST_F(AgradRev, Rev_save_varis_tuple_args) {
430+
const std::tuple<> empty;
431+
const auto data = std::make_tuple(1, Eigen::VectorXd::Ones(2));
432+
std::vector<vari*> data_storage(2, nullptr);
433+
vari** data_ptr = stan::math::save_varis(data_storage.data(), empty, data);
434+
EXPECT_EQ(data_storage.data(), data_ptr);
435+
EXPECT_EQ(nullptr, data_storage[0]);
436+
EXPECT_EQ(nullptr, data_storage[1]);
437+
438+
var before = 1.0;
439+
var first = 2.0;
440+
Eigen::Matrix<var, Eigen::Dynamic, 1> vars(2);
441+
vars << 3.0, 4.0;
442+
var last = 5.0;
443+
var after = 6.0;
444+
auto nested = std::make_tuple(first, std::make_tuple(vars, 7), last);
445+
std::vector<vari*> storage(8, nullptr);
446+
447+
vari** ptr = stan::math::save_varis(storage.data(), before, nested,
448+
std::make_tuple(after));
449+
450+
std::vector<vari*> expected{before.vi_, first.vi_, vars(0).vi_,
451+
vars(1).vi_, last.vi_, after.vi_};
452+
EXPECT_EQ(storage.data() + expected.size(), ptr);
453+
for (size_t i = 0; i < expected.size(); ++i) {
454+
EXPECT_EQ(expected[i], storage[i]);
455+
}
456+
EXPECT_EQ(nullptr, storage[expected.size()]);
457+
EXPECT_EQ(nullptr, storage[expected.size() + 1]);
458+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include <stan/math.hpp>
2+
#include <test/unit/math/rev/util.hpp>
3+
#include <gtest/gtest.h>
4+
#include <tuple>
5+
#include <vector>
6+
7+
namespace {
8+
9+
struct sum_with_laplace_options {
10+
template <typename Options>
11+
auto operator()(const std::vector<int>& slice, std::size_t start,
12+
std::size_t end, std::ostream* msgs,
13+
const stan::math::var& shared,
14+
const Options& laplace_options) const {
15+
return slice.size()
16+
* (shared + static_cast<double>(std::get<2>(laplace_options)));
17+
}
18+
};
19+
20+
// Regression test for https://github.com/stan-dev/math/issues/3359.
21+
TEST_F(AgradRev, reduce_sum_accepts_const_laplace_options_tuple) {
22+
stan::math::var shared = 1.0;
23+
const auto laplace_options = stan::math::generate_laplace_options(1);
24+
25+
stan::math::var result = stan::math::reduce_sum<sum_with_laplace_options>(
26+
std::vector<int>{0, 1}, 1, nullptr, shared, laplace_options);
27+
28+
const double expected = 2.0 * (shared.val() + std::get<2>(laplace_options));
29+
EXPECT_FLOAT_EQ(expected, result.val());
30+
result.grad();
31+
EXPECT_FLOAT_EQ(2.0, shared.adj());
32+
}
33+
34+
} // namespace

0 commit comments

Comments
 (0)