-
-
Notifications
You must be signed in to change notification settings - Fork 187
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
Conversation
Jenkins Console Log Machine informationProductName: Mac OS X ProductVersion: 10.11.6 BuildVersion: 15G22010CPU: G++: Clang: |
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 |
There was a problem hiding this 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
.
stan/math/prim/err/check_ordered.hpp
Outdated
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
stan/math/prim/err/check_ordered.hpp
Outdated
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++) { |
There was a problem hiding this comment.
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; }); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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();
}
There was a problem hiding this comment.
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?
stan/math/prim/fun/dot_self.hpp
Outdated
* @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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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; |
There was a problem hiding this comment.
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))
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @tparam Vec type deriving from `Eigen::MatgrixBase` with rows or columns | |
* @tparam Vec type deriving from `Eigen::MatrixBase` with rows or columns |
std::vector<value_type_t<Vec>> vec | ||
= vec_concat(std::forward<VecArgs>(args)...); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
stan/math/prim/fun/dot_self.hpp
Outdated
check_vector("dot_self", "v", v); | ||
template <typename EigMat, | ||
require_eigen_vt<std::is_arithmetic, EigMat>* = nullptr> | ||
inline double dot_self(EigMat&& v) { |
There was a problem hiding this comment.
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);
.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
… doing constraints
…xp1~20180509124008.99 (branches/release_50)
…xp1~20180509124008.99 (branches/release_50)
…gs/RELEASE_500/final)
Jenkins Console Log Machine informationProductName: Mac OS X ProductVersion: 10.11.6 BuildVersion: 15G22010CPU: G++: Clang: |
There was a problem hiding this 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.
stan/math/prim/err/check_ordered.hpp
Outdated
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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; }); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any results from benchmarking?
…4.1 (tags/RELEASE_600/final)
…4.1 (tags/RELEASE_600/final)
…4.1 (tags/RELEASE_600/final)
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 |
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
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
./runTests.py test/unit
)make test-headers
)make test-math-dependencies
)make doxygen
)make cpplint
)the code is written in idiomatic C++ and changes are documented in the doxygen
the new changes are tested