Skip to content

Commit 6ec4b0b

Browse files
authored
Merge pull request #3342 from stan-dev/serde/sum-to-zero-matrix
2 parents 3629c5a + 616aa0e commit 6ec4b0b

7 files changed

Lines changed: 254 additions & 13 deletions

src/stan/io/deserializer.hpp

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -583,20 +583,19 @@ class deserializer {
583583

584584
/**
585585
* Return the next zero sum vector of the specified size (using one fewer
586-
* unconstrained scalars), incrementing the specified reference with the
587-
* log absolute Jacobian determinant (no adjustment, in this case).
586+
* unconstrained scalars). This particular transformation is linear, not
587+
* requiring a Jacobian adjustment.
588588
*
589-
* <p>See <code>stan::math::sum_to_zero_constrain(Eigen::Matrix,T&)</code>.
589+
* <p>See <code>stan::math::sum_to_zero_constrain(Eigen::Matrix, T&)</code>.
590590
*
591591
* @tparam Ret The type to return.
592592
* @tparam Jacobian Whether to increment the log of the absolute Jacobian
593593
* determinant of the transform.
594594
* @tparam LP Type of log probability.
595-
* @tparam Sizes A parameter pack of integral types.
596-
* @param lp The reference to the variable holding the log
595+
* @param lp (Unused) The reference to the variable holding the log density.
597596
* @param size Number of cells in zero sum vector to generate.
598597
* @return The next zero sum of the specified size.
599-
* @throws std::invalid_argument if number of dimensions (`k`) is zero
598+
* @throws std::invalid_argument if `size` is zero
600599
*/
601600
template <typename Ret, bool Jacobian, typename LP,
602601
require_not_std_vector_t<Ret>* = nullptr>
@@ -606,10 +605,37 @@ class deserializer {
606605
this->read<Ret>(size - 1), lp);
607606
}
608607

608+
/**
609+
* Return the next zero sum matrix of the specified dimensions (requires
610+
* (N - 1) * (M - 1) unconstrained scalars).
611+
* This particular transformation is linear, not requiring a Jacobian
612+
* adjustment.
613+
*
614+
* <p>See <code>stan::math::sum_to_zero_constrain(Eigen::Matrix, T&)</code>.
615+
*
616+
* @tparam Ret The type to return.
617+
* @tparam Jacobian Whether to increment the log of the absolute Jacobian
618+
* determinant of the transform.
619+
* @tparam LP Type of log probability.
620+
* @param lp (Unused) The reference to the variable holding the log density.
621+
* @param N Number of rows in zero sum matrix to generate.
622+
* @param M Number of columns in zero sum matrix to generate.
623+
* @return The next zero sum of the specified size.
624+
* @throws std::invalid_argument either `N` or `M` is zero
625+
*/
626+
template <typename Ret, bool Jacobian, typename LP,
627+
require_matrix_t<Ret>* = nullptr>
628+
inline auto read_constrain_sum_to_zero(LP& lp, size_t N, size_t M) {
629+
stan::math::check_positive("read_sum_to_zero", "N", N);
630+
stan::math::check_positive("read_sum_to_zero", "M", M);
631+
return stan::math::sum_to_zero_constrain<Jacobian>(
632+
this->read<conditional_var_val_t<Ret, matrix_t>>(N - 1, M - 1), lp);
633+
}
634+
609635
/**
610636
* Return the next zero sum vector of the specified size (using one fewer
611-
* unconstrained scalars), incrementing the specified reference with the
612-
* log absolute Jacobian determinant (no adjustment, in this case).
637+
* unconstrained scalars). This particular transformation is linear, not
638+
* requiring a Jacobian adjustment.
613639
*
614640
* <p>See <code>stan::math::sum_to_zero_constrain(Eigen::Matrix,T&)</code>.
615641
*
@@ -1219,18 +1245,32 @@ class deserializer {
12191245
}
12201246

12211247
/**
1222-
* Read a serialized sum_to_zero vector and unconstrain it
1248+
* Read a serialized sum_to_zero vector and unconstrain it.
12231249
*
12241250
* @tparam Ret Type of output
1225-
* @return Unconstrained vector
1251+
* @param size Vector size
1252+
* @return Unconstrained vector of length (size - 1)
12261253
*/
12271254
template <typename Ret, require_not_std_vector_t<Ret>* = nullptr>
12281255
inline auto read_free_sum_to_zero(size_t size) {
12291256
return stan::math::sum_to_zero_free(this->read<Ret>(size));
12301257
}
12311258

12321259
/**
1233-
* Read serialized zero-sum vectors and unconstrain them
1260+
* Read a serialized sum_to_zero matrix and unconstrain it.
1261+
*
1262+
* @tparam Ret Type of output
1263+
* @param N Rows of matrix
1264+
* @param M Cols of matrix
1265+
* @return Unconstrained matrix of size (N-1) x (M-1)
1266+
*/
1267+
template <typename Ret, require_matrix_t<Ret>* = nullptr>
1268+
inline auto read_free_sum_to_zero(size_t N, size_t M) {
1269+
return stan::math::sum_to_zero_free(this->read<Ret>(N, M));
1270+
}
1271+
1272+
/**
1273+
* Read serialized zero-sum vectors and unconstrain them.
12341274
*
12351275
* @tparam Ret Type of output
12361276
* @tparam Sizes Types of dimensions of output

src/test/unit/io/deserializer_free_test.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,24 @@ struct SumToZeroConstrain {
265265

266266
TEST(deserializer_vector, read_free_sum_to_zero) {
267267
using stan::test::deserializer_test;
268+
deserializer_test<Eigen::VectorXd, SumToZeroConstrain>(std::make_tuple(1));
268269
deserializer_test<Eigen::VectorXd, SumToZeroConstrain>(std::make_tuple(4));
269270
deserializer_test<std::vector<Eigen::VectorXd>, SumToZeroConstrain>(
270271
std::make_tuple(2, 4));
271272
deserializer_test<std::vector<std::vector<Eigen::VectorXd>>,
272273
SumToZeroConstrain>(std::make_tuple(3, 2, 4));
273274
}
274275

276+
TEST(deserializer_matrix, read_free_sum_to_zero) {
277+
using stan::test::deserializer_test;
278+
deserializer_test<Eigen::MatrixXd, SumToZeroConstrain>(std::make_tuple(1, 1));
279+
deserializer_test<Eigen::MatrixXd, SumToZeroConstrain>(std::make_tuple(4, 5));
280+
deserializer_test<std::vector<Eigen::MatrixXd>, SumToZeroConstrain>(
281+
std::make_tuple(2, 4, 5));
282+
deserializer_test<std::vector<std::vector<Eigen::MatrixXd>>,
283+
SumToZeroConstrain>(std::make_tuple(3, 2, 4, 2));
284+
}
285+
275286
// ordered
276287
template <typename Ret>
277288
struct OrderedConstrain {

src/test/unit/io/deserializer_stdvector_test.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,50 @@ TEST(deserializer_array, sum_to_zero) {
175175
}
176176
}
177177

178+
TEST(deserializer_array, sum_to_zero_matrix) {
179+
std::vector<int> theta_i;
180+
std::vector<double> theta;
181+
for (size_t i = 0; i < 100U; ++i)
182+
theta.push_back(static_cast<double>(i));
183+
184+
stan::io::deserializer<double> deserializer1(theta, theta_i);
185+
stan::io::deserializer<double> deserializer2(theta, theta_i);
186+
187+
// no jac
188+
{
189+
double lp_ref = 0.0;
190+
double lp = 0.0;
191+
auto y
192+
= deserializer1
193+
.read_constrain_sum_to_zero<std::vector<Eigen::MatrixXd>, false>(
194+
lp, 4, 3, 2);
195+
for (size_t i = 0; i < 4; ++i) {
196+
stan::test::expect_near_rel(
197+
"test_std_vector_deserializer", y[i],
198+
deserializer2.read_constrain_sum_to_zero<Eigen::MatrixXd, false>(
199+
lp_ref, 3, 2));
200+
}
201+
EXPECT_FLOAT_EQ(lp_ref, lp);
202+
}
203+
204+
// jac
205+
{
206+
double lp_ref = 0.0;
207+
double lp = 0.0;
208+
auto y
209+
= deserializer1
210+
.read_constrain_sum_to_zero<std::vector<Eigen::MatrixXd>, true>(
211+
lp, 4, 3, 2);
212+
for (size_t i = 0; i < 4; ++i) {
213+
stan::test::expect_near_rel(
214+
"test_std_vector_deserializer", y[i],
215+
deserializer2.read_constrain_sum_to_zero<Eigen::MatrixXd, true>(
216+
lp_ref, 3, 2));
217+
}
218+
EXPECT_FLOAT_EQ(lp_ref, lp);
219+
}
220+
}
221+
178222
// ordered
179223

180224
TEST(deserializer_array, ordered) {

src/test/unit/io/deserializer_test.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,63 @@ TEST(deserializer_vector, sum_to_zero_jacobian) {
714714
EXPECT_FLOAT_EQ(lp_ref, lp);
715715
}
716716

717+
TEST(deserializer_matrix, sum_to_zero_constrain) {
718+
std::vector<int> theta_i;
719+
std::vector<double> theta;
720+
theta.push_back(3.0);
721+
theta.push_back(-1.0);
722+
theta.push_back(-2.0);
723+
theta.push_back(0.0);
724+
stan::io::deserializer<double> deserializer(theta, theta_i);
725+
double lp = 0.0;
726+
Eigen::MatrixXd reference
727+
= stan::math::sum_to_zero_constrain(stan::math::to_matrix(theta, 2, 2));
728+
Eigen::MatrixXd phi(
729+
deserializer.read_constrain_sum_to_zero<Eigen::MatrixXd, false>(lp, 3,
730+
3));
731+
for (int n = 0; n < phi.size(); ++n) {
732+
EXPECT_FLOAT_EQ(reference(n), phi(n));
733+
}
734+
}
735+
736+
TEST(deserializer_matrix, sum_to_zero_constrain_empty) {
737+
std::vector<int> theta_i;
738+
std::vector<double> theta;
739+
740+
stan::io::deserializer<double> deserializer(theta, theta_i);
741+
double lp = 0.0;
742+
743+
Eigen::MatrixXd phi(
744+
deserializer.read_constrain_sum_to_zero<Eigen::MatrixXd, false>(lp, 1,
745+
1));
746+
747+
EXPECT_EQ(1, phi.rows());
748+
EXPECT_EQ(1, phi.cols());
749+
for (int n = 0; n < phi.size(); ++n) {
750+
EXPECT_FLOAT_EQ(0.0, phi(n));
751+
}
752+
}
753+
754+
TEST(deserializer_matrix, sum_to_zero_jacobian) {
755+
std::vector<int> theta_i;
756+
std::vector<double> theta;
757+
theta.push_back(3.0);
758+
theta.push_back(-1.0);
759+
theta.push_back(-2.0);
760+
theta.push_back(0.0);
761+
stan::io::deserializer<double> deserializer(theta, theta_i);
762+
double lp = 0.0;
763+
double lp_ref = 0.0;
764+
Eigen::MatrixXd reference = stan::math::sum_to_zero_constrain(
765+
stan::math::to_matrix(theta, 2, 2), lp_ref);
766+
Eigen::MatrixXd phi(
767+
deserializer.read_constrain_sum_to_zero<Eigen::MatrixXd, true>(lp, 3, 3));
768+
for (int n = 0; n < phi.size(); ++n) {
769+
EXPECT_FLOAT_EQ(reference(n), phi(n));
770+
}
771+
EXPECT_FLOAT_EQ(lp_ref, lp);
772+
}
773+
717774
// ordered
718775

719776
TEST(deserializer_vector, ordered_constrain) {

src/test/unit/io/deserializer_var_test.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,50 @@ TEST(deserializer_vector, sum_to_zero_jacobian) {
640640
}
641641
EXPECT_FLOAT_EQ(lp_ref.val(), lp.val());
642642
}
643+
644+
TEST(deserializer_matrix, sum_to_zero_constrain) {
645+
std::vector<int> theta_i;
646+
std::vector<stan::math::var> theta;
647+
theta.push_back(3.0);
648+
theta.push_back(-1.0);
649+
theta.push_back(-2.0);
650+
theta.push_back(0.0);
651+
stan::io::deserializer<stan::math::var> deserializer(theta, theta_i);
652+
stan::math::var lp = 0;
653+
Eigen::Matrix<stan::math::var, Eigen::Dynamic, Eigen::Dynamic> reference
654+
= stan::math::sum_to_zero_constrain(stan::math::to_matrix(theta, 2, 2));
655+
Eigen::Matrix<stan::math::var, Eigen::Dynamic, Eigen::Dynamic> phi(
656+
deserializer.read_constrain_sum_to_zero<
657+
Eigen::Matrix<stan::math::var, Eigen::Dynamic, Eigen::Dynamic>,
658+
false>(lp, 3, 3));
659+
for (int n = 0; n < phi.size(); ++n) {
660+
EXPECT_FLOAT_EQ(reference(n).val(), phi(n).val());
661+
}
662+
}
663+
664+
TEST(deserializer_matrix, sum_to_zero_jacobian) {
665+
std::vector<int> theta_i;
666+
std::vector<stan::math::var> theta;
667+
theta.push_back(3.0);
668+
theta.push_back(-1.0);
669+
theta.push_back(-2.0);
670+
theta.push_back(0.0);
671+
stan::io::deserializer<stan::math::var> deserializer(theta, theta_i);
672+
stan::math::var lp = 0.0;
673+
stan::math::var lp_ref = 0.0;
674+
Eigen::Matrix<stan::math::var, Eigen::Dynamic, Eigen::Dynamic> reference
675+
= stan::math::sum_to_zero_constrain(stan::math::to_matrix(theta, 2, 2),
676+
lp_ref);
677+
Eigen::Matrix<stan::math::var, Eigen::Dynamic, Eigen::Dynamic> phi(
678+
deserializer.read_constrain_sum_to_zero<
679+
Eigen::Matrix<stan::math::var, Eigen::Dynamic, Eigen::Dynamic>, true>(
680+
lp, 3, 3));
681+
for (int n = 0; n < phi.size(); ++n) {
682+
EXPECT_FLOAT_EQ(reference(n).val(), phi(n).val());
683+
}
684+
EXPECT_FLOAT_EQ(lp_ref.val(), lp.val());
685+
}
686+
643687
// ordered
644688

645689
TEST(deserializer_vector, ordered_constrain) {

src/test/unit/io/deserializer_varmat_test.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,42 @@ TEST(deserializer, read_constrain_sum_to_zero_jacobian) {
363363
EXPECT_FLOAT_EQ(lp_ref.val(), lp.val());
364364
}
365365

366+
TEST(deserializer_var_matrix, read_constrain_sum_to_zero_constrain) {
367+
std::vector<int> theta_i;
368+
std::vector<stan::math::var> theta;
369+
theta.push_back(-2.0);
370+
theta.push_back(3.0);
371+
theta.push_back(-1.0);
372+
theta.push_back(0.0);
373+
stan::io::deserializer<stan::math::var> deserializer(theta, theta_i);
374+
stan::math::var lp = 0.0;
375+
auto reference
376+
= stan::math::sum_to_zero_constrain(stan::math::to_matrix(theta, 2, 2));
377+
auto y
378+
= deserializer.read_constrain_sum_to_zero<var_matrix_t, false>(lp, 3, 3);
379+
EXPECT_TRUE((std::is_same<var_matrix_t, decltype(y)>::value));
380+
stan::test::expect_near_rel("deserializer tests", reference.val(), y.val());
381+
}
382+
383+
TEST(deserializer_var_matrix, read_constrain_sum_to_zero_jacobian) {
384+
std::vector<int> theta_i;
385+
std::vector<stan::math::var> theta;
386+
theta.push_back(-2.0);
387+
theta.push_back(3.0);
388+
theta.push_back(-1.0);
389+
theta.push_back(0.0);
390+
stan::io::deserializer<stan::math::var> deserializer(theta, theta_i);
391+
stan::math::var lp_ref = 0.0;
392+
stan::math::var lp = 0.0;
393+
auto reference = stan::math::sum_to_zero_constrain(
394+
stan::math::to_matrix(theta, 2, 2), lp_ref);
395+
auto y
396+
= deserializer.read_constrain_sum_to_zero<var_matrix_t, false>(lp, 3, 3);
397+
EXPECT_TRUE((std::is_same<var_matrix_t, decltype(y)>::value));
398+
stan::test::expect_near_rel("deserializer tests", reference.val(), y.val());
399+
EXPECT_FLOAT_EQ(lp_ref.val(), lp.val());
400+
}
401+
366402
// ordered
367403

368404
TEST(deserializer, read_constrain_ordered_constrain) {

src/test/unit/io/serializer_test.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,15 @@ TEST(serializer_vectorized, write_free_sum_to_zero) {
522522
SumToZeroConstrain>(std::make_tuple(3, 2, 4));
523523
}
524524

525+
TEST(serializer_vectorized, write_free_sum_to_zero_mat) {
526+
using stan::test::serializer_test;
527+
serializer_test<Eigen::MatrixXd, SumToZeroConstrain>(std::make_tuple(4, 5));
528+
serializer_test<std::vector<Eigen::MatrixXd>, SumToZeroConstrain>(
529+
std::make_tuple(2, 4, 3));
530+
serializer_test<std::vector<std::vector<Eigen::MatrixXd>>,
531+
SumToZeroConstrain>(std::make_tuple(3, 2, 4, 2));
532+
}
533+
525534
// ordered
526535

527536
template <typename Ret>
@@ -692,7 +701,7 @@ struct StochasticCol {
692701
};
693702
}
694703
};
695-
TEST(deserializer_vector, read_stochastic_column_matrix) {
704+
TEST(serializer_vectorized, read_stochastic_column_matrix) {
696705
using stan::test::serializer_test;
697706
serializer_test<Eigen::MatrixXd, StochasticCol>(std::make_tuple(3, 3));
698707
serializer_test<std::vector<Eigen::MatrixXd>, StochasticCol>(
@@ -716,7 +725,7 @@ struct StochasticRow {
716725
};
717726
}
718727
};
719-
TEST(deserializer_vector, read_stochastic_row_matrix) {
728+
TEST(serializer_vectorized, read_stochastic_row_matrix) {
720729
using stan::test::serializer_test;
721730
serializer_test<Eigen::MatrixXd, StochasticRow>(std::make_tuple(3, 3));
722731
serializer_test<std::vector<Eigen::MatrixXd>, StochasticRow>(

0 commit comments

Comments
 (0)