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

Allow constrain and free functions to work on Eigen types and std vectors #1766

Closed
wants to merge 29 commits into from

Conversation

SteveBronder
Copy link
Collaborator

Summary

I was working on a PR for sparse matrices up in the Stan library and found a few places where we can use vectorized ops in the reader and writer. This PR adds vectorized versions of the *_constrain and *_free functions so they can take in eigen types and standard vectors. This also adds testing for the *_free functions which idt existed before.

Tests

Tests can be run with

./runTests.py ./test/unit/math/mix/fun/ -f constrain_test

Side Effects

The *_constrain and *_free can now take in vector like types.

Checklist

  • Math issue #(issue number)

  • Copyright holder: Steve Bronder

    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

@SteveBronder SteveBronder changed the title Cleanup/constrains Allow constrain and free functions to work in Eigen types and std vectors Mar 9, 2020
@SteveBronder SteveBronder changed the title Allow constrain and free functions to work in Eigen types and std vectors Allow constrain and free functions to work on Eigen types and std vectors Mar 9, 2020
@stan-buildbot
Copy link
Contributor


Name Old Result New Result Ratio Performance change( 1 - new / old )
gp_pois_regr/gp_pois_regr.stan 4.86 4.8 1.01 1.32% faster
low_dim_corr_gauss/low_dim_corr_gauss.stan 0.02 0.02 0.97 -3.37% slower
eight_schools/eight_schools.stan 0.09 0.09 0.97 -2.71% slower
gp_regr/gp_regr.stan 0.22 0.22 1.01 1.46% faster
irt_2pl/irt_2pl.stan 6.48 6.45 1.01 0.52% faster
performance.compilation 87.35 86.22 1.01 1.29% faster
low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan 7.54 7.58 1.0 -0.48% slower
pkpd/one_comp_mm_elim_abs.stan 20.83 19.99 1.04 4.02% faster
sir/sir.stan 93.9 91.58 1.03 2.48% faster
gp_regr/gen_gp_data.stan 0.05 0.05 0.97 -2.75% slower
low_dim_gauss_mix/low_dim_gauss_mix.stan 2.95 2.98 0.99 -1.04% slower
pkpd/sim_one_comp_mm_elim_abs.stan 0.31 0.33 0.94 -6.78% slower
arK/arK.stan 1.9 1.75 1.09 7.99% faster
arma/arma.stan 0.69 0.66 1.04 4.23% faster
garch/garch.stan 0.52 0.62 0.84 -19.18% slower
Mean result: 0.994623494379

Jenkins Console Log
Blue Ocean
Commit hash: 72c877f


Machine information ProductName: Mac OS X ProductVersion: 10.11.6 BuildVersion: 15G22010

CPU:
Intel(R) Xeon(R) CPU E5-1680 v2 @ 3.00GHz

G++:
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.6.0
Thread model: posix

Clang:
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.6.0
Thread model: posix

@SteveBronder
Copy link
Collaborator Author

This is ready for review! If the reviewer wants me to break this up into multiple PRs that's fine, though the changes are mostly the same across a lot of the files

Copy link
Contributor

@t4c1 t4c1 left a comment

Choose a reason for hiding this comment

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

This PR is indeed quite large. It is managable, but next time plan for smaller ones. I tried not to repeat comments, so most of them apply to multiple places in code.

I think it only makes sense to use auto to replace types or metaprograms that are too long or confusing. it is subjective, where exactely is the threshold is, but built-in types (such as int) should never be replaced by auto.

const std::vector<T_y>& y) {
for (size_t n = 1; n < y.size(); n++) {
template <typename Vec, require_vector_like_t<Vec>* = nullptr>
inline void check_ordered(const char* function, const char* name, Vec&& y) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Arguments should be passed into functions as const references unless there is a good reason to pass them in some other way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

What's the reason behind prefering const&? I kind of like universal references since they preserve value types

Copy link
Contributor

Choose a reason for hiding this comment

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

It is simply the matter of using the simplest tool that does the job. It makes code easier to understand (especially to someone new to the codebase). By specifying const& anyone reading the code immediately knows that the function does not modify the argument.

What do you mean by preserving value types? In what cases is it beneficial?

Copy link
Member

Choose a reason for hiding this comment

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

I'll second @t4c1's comment. This is purely for readability. Yes, you can use universal references, but we don't have that propagated throughout the Math library. This PR doesn't need this particular change and if you wanted to change that all at once, we can do that (in a separate issue + PR).

for (size_t n = 1; n < y.size(); n++) {
template <typename Vec, require_vector_like_t<Vec>* = nullptr>
inline void check_ordered(const char* function, const char* name, Vec&& y) {
for (auto n = 1; n < y.size(); n++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

No reason to use auto here. int is both shorter and clearer.

inline auto divide(const Vec& x, Scal c) {
std::vector<value_type_t<Vec>> ret_x(x.size());
std::transform(x.begin(), x.end(), ret_x.begin(),
[&c](auto&& x_iter) { return x_iter / c; });
Copy link
Contributor

Choose a reason for hiding this comment

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

This could use apply_vector_unary and be combined with previous overload.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Which overload are you referencing here?

Copy link
Contributor

Choose a reason for hiding this comment

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

template <typename Mat, typename Scal, typename = require_eigen_t<Mat>,
          typename = require_stan_scalar_t<Scal>,
          typename = require_all_not_var_t<scalar_type_t<Mat>, Scal>>
inline auto divide(const Mat& m, Scal c) {
  return (m / c).eval();
}

Copy link
Member

Choose a reason for hiding this comment

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

@SteveBronder, did you see @t4c1's last comment here?

* @throw std::domain_error If v is not vector dimensioned.
*/
template <typename StdVec, require_std_vector_t<StdVec>* = nullptr>
inline auto dot_self(StdVec&& x) {
Copy link
Contributor

Choose a reason for hiding this comment

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

[optional] In my opinion

template <Scalar>
inline auto dot_self(const std::vector<Scalar>& x)

is easier to understand than using require, while producing the same result.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also you can use apply_vector_unary to combine this overload with the next one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I saw you changed this in your PR so I'll wait on these till your PR is merged

template <typename StdVec, require_std_vector_t<StdVec>* = nullptr>
inline auto dot_self(StdVec&& x) {
value_type_t<StdVec> sum = 0.0;
for (auto&& i : x) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefere value_type_t<StdVec> here. In this instance I might be ok with auto, but since i is scalar we don't need reference (especially not universal reference).


size_type k = x.size();
Matrix<T, Dynamic, 1> y(k);
auto k = x.size();
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for auto here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I thought so too but it looks like users can change this and it's default is

EIGEN_DEFAULT_DENSE_INDEX_TYPE - the type for column and row indices in matrices, vectors and array (DenseBase::Index). Set to std::ptrdiff_t by default.

So I know it sounds weird but I kind of like auto here. Though casting to int is no biggie (idt) so I'm fine with just calling it int

https://eigen.tuxfamily.org/dox/TopicPreprocessorDirectives.html

Copy link
Contributor

Choose a reason for hiding this comment

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

In that case you can use Eigen::Index to avoid the cast.

Comment on lines 29 to 33
size_type k = x.size();
Matrix<T, Dynamic, 1> y(k);
auto k = x.size();
plain_type_t<Vec> y(k);
if (k == 0) {
return y;
}
y[0] = exp(x[0]);
for (size_type i = 1; i < k; ++i) {
for (auto i = 1; i < k; ++i) {
y[i] = y[i - 1] + exp(x[i]);
}
return y;
Copy link
Contributor

Choose a reason for hiding this comment

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

return cumulative_sum(exp(x))?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I need to look into this but that was giving me the test failure

test/unit/math/prim/fun/positive_ordered_transform_test.cpp:25: Failure
Expected equality of these values:
  exp(1.0) + exp(-2.0) + exp(-5.0)
    Which is: 2.86036
  y[2]
    Which is: 2.86036

Copy link
Contributor

Choose a reason for hiding this comment

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

Well the test uses EXPECT_EQ for floating point numbers. It would fail for anything that is not exactely the same expression. The test should be modified to have some tolerance.

@@ -20,28 +20,24 @@ namespace math {
*
* The transform is based on a centered stick-breaking process.
*
* @tparam T type of elements in the vector
* @tparam Vec type deriving from `Eigen::MatgrixBase` with rows or columns
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @tparam Vec type deriving from `Eigen::MatgrixBase` with rows or columns
* @tparam Vec type deriving from `Eigen::MatrixBase` with rows or columns

Comment on lines +33 to +34
std::vector<value_type_t<Vec>> vec
= vec_concat(std::forward<VecArgs>(args)...);
Copy link
Contributor

Choose a reason for hiding this comment

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

[optional] This is a bit inefficient. The result vector vec should reserve its final size in advance.

auto g3(const T& x) {
stan::value_type_t<T> lp = 0;
auto x_cons = stan::math::identity_constrain(x, lp);
auto x_free = stan::math::identity_free(x_cons);
Copy link
Contributor

Choose a reason for hiding this comment

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

You are only testing identity_constrain and identity_free together. Each should also be tested individually.

check_vector("dot_self", "v", v);
template <typename EigMat,
require_eigen_vt<std::is_arithmetic, EigMat>* = nullptr>
inline double dot_self(EigMat&& v) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not reviewing, but would very much appreciate seeing this renamed to EigVec (what I'd really like is V, but that's a bigger discussion we need to take up).

It is not an Eigen matrix, it's an Eigen vector. Eigen::Matrix<T, 1, -1> and Eigen::Matrix<T, -1, 1> and Eigen::Matrix<T, -1, -1> are three completely independent specializations of Eigen::Matrix<T, R, C>. Only the last of the tree is a matrix. The other two are vectors and do not even support T& operator()(ptrdiff_t, ptrdiff_t);.

Copy link
Contributor

Choose a reason for hiding this comment

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

I find it weird, but there are even some test checking that dot_self works with (non-vector) matrices.

@@ -13,13 +13,32 @@ namespace math {
* <p>This function is a no-op and mainly useful as a placeholder
* in auto-generated code.
*
* @tparam T type of value
* @param[in] y value
* @tparam Scalar type of value
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not a scalar any more, because now scalars include complex numbers. Going back to T works. Or if you insist on verbosely naming types, it should be Real.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why not? Complex scalars are scalars as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

The problem's the other way around. These functions won't work for complex inputs, so the required type should be called Real, not Scalar.

@stan-buildbot
Copy link
Contributor


Name Old Result New Result Ratio Performance change( 1 - new / old )
gp_pois_regr/gp_pois_regr.stan 4.96 4.87 1.02 1.95% faster
low_dim_corr_gauss/low_dim_corr_gauss.stan 0.02 0.02 0.97 -2.71% slower
eight_schools/eight_schools.stan 0.09 0.09 1.01 0.59% faster
gp_regr/gp_regr.stan 0.22 0.22 1.0 0.03% faster
irt_2pl/irt_2pl.stan 6.5 6.44 1.01 0.84% faster
performance.compilation 88.52 86.1 1.03 2.73% faster
low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan 7.53 7.53 1.0 0.07% faster
pkpd/one_comp_mm_elim_abs.stan 21.06 21.29 0.99 -1.09% slower
sir/sir.stan 93.3 90.74 1.03 2.74% faster
gp_regr/gen_gp_data.stan 0.05 0.05 0.99 -1.09% slower
low_dim_gauss_mix/low_dim_gauss_mix.stan 2.95 2.99 0.98 -1.54% slower
pkpd/sim_one_comp_mm_elim_abs.stan 0.31 0.31 1.0 -0.15% slower
arK/arK.stan 1.74 1.73 1.0 0.48% faster
arma/arma.stan 0.66 0.66 1.0 -0.41% slower
garch/garch.stan 0.52 0.62 0.84 -19.17% slower
Mean result: 0.991128698371

Jenkins Console Log
Blue Ocean
Commit hash: eb24ce2


Machine information ProductName: Mac OS X ProductVersion: 10.11.6 BuildVersion: 15G22010

CPU:
Intel(R) Xeon(R) CPU E5-1680 v2 @ 3.00GHz

G++:
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.6.0
Thread model: posix

Clang:
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.6.0
Thread model: posix

Copy link
Member

@syclik syclik left a comment

Choose a reason for hiding this comment

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

@SteveBronder, it looks like there's work to do on your part for this PR.

const std::vector<T_y>& y) {
for (size_t n = 1; n < y.size(); n++) {
template <typename Vec, require_vector_like_t<Vec>* = nullptr>
inline void check_ordered(const char* function, const char* name, Vec&& y) {
Copy link
Member

Choose a reason for hiding this comment

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

I'll second @t4c1's comment. This is purely for readability. Yes, you can use universal references, but we don't have that propagated throughout the Math library. This PR doesn't need this particular change and if you wanted to change that all at once, we can do that (in a separate issue + PR).


template <typename Vec, require_vector_like_t<Vec>* = nullptr>
inline void check_positive_ordered(const char* function, const char* name,
Vec&& y) {
Copy link
Member

Choose a reason for hiding this comment

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

Same comment about using the "universal reference."

inline auto divide(const Vec& x, Scal c) {
std::vector<value_type_t<Vec>> ret_x(x.size());
std::transform(x.begin(), x.end(), ret_x.begin(),
[&c](auto&& x_iter) { return x_iter / c; });
Copy link
Member

Choose a reason for hiding this comment

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

@SteveBronder, did you see @t4c1's last comment here?

inline T identity_constrain(const T& x) {
return x;
template <typename Scalar, require_all_stan_scalar_t<Scalar>* = nullptr>
inline decltype(auto) identity_constrain(Scalar&& x) {
Copy link
Member

Choose a reason for hiding this comment

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

Any results from benchmarking?

@SteveBronder
Copy link
Collaborator Author

I got this to work on an upstream stan branch and didn't see any performance improvements over the basic benchmarks and a few in examples so going to close

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.

5 participants