-
-
Notifications
You must be signed in to change notification settings - Fork 200
Feature/issue 125 cholesky derivative #135
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
Changes from all commits
e72d17b
5bb01ab
019d64b
eb2cab4
22146e9
e59c4cc
3bcbdb6
7851547
0d9e659
fc5d674
25b093c
38f5a82
f173b77
ef9c518
c6e4822
41c35ee
be37bb7
b1749c4
b152b37
8554a04
31e5416
61678f0
3f4f966
14815b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| #ifndef STAN_MATH_REV_MAT_FUN_CHOLESKY_DECOMPOSE_HPP | ||
| #define STAN_MATH_REV_MAT_FUN_CHOLESKY_DECOMPOSE_HPP | ||
|
|
||
| #include <stan/math/prim/mat/fun/Eigen.hpp> | ||
| #include <stan/math/prim/mat/fun/typedefs.hpp> | ||
| #include <stan/math/prim/mat/fun/cholesky_decompose.hpp> | ||
| #include <stan/math/rev/scal/fun/value_of_rec.hpp> | ||
| #include <stan/math/rev/scal/fun/value_of.hpp> | ||
| #include <stan/math/rev/core.hpp> | ||
| #include <stan/math/prim/mat/fun/value_of_rec.hpp> | ||
| #include <stan/math/prim/mat/err/check_pos_definite.hpp> | ||
| #include <stan/math/prim/mat/err/check_square.hpp> | ||
| #include <stan/math/prim/mat/err/check_symmetric.hpp> | ||
|
|
||
| namespace stan { | ||
| namespace math { | ||
|
|
||
| class cholesky_decompose_v_vari : public vari { | ||
| public: | ||
| int M_; // A.rows() = A.cols() | ||
| vari** variRefA_; | ||
| vari** variRefL_; | ||
|
|
||
| /* ctor for cholesky function | ||
| * | ||
| * Stores varis for A | ||
| * Instantiates and stores varis for L | ||
| * Instantiates and stores dummy vari for | ||
| * upper triangular part of var result returned | ||
| * in cholesky_decompose function call | ||
| * | ||
| * variRefL aren't on the chainable | ||
| * autodiff stack, only used for storage | ||
| * and computation. Note that varis for | ||
| * L are constructed externally in | ||
| * cholesky_decompose. | ||
| * | ||
| * @param matrix A | ||
| * @param matrix L, cholesky factor of A | ||
| * */ | ||
| cholesky_decompose_v_vari(const Eigen::Matrix<var, -1, -1>& A, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should choose either |
||
| const Eigen::Matrix<double, -1, -1>& L_A) | ||
| : vari(0.0), | ||
| M_(A.rows()), | ||
| variRefA_(ChainableStack::memalloc_.alloc_array<vari*> | ||
| (A.rows() * (A.rows() + 1) / 2)), | ||
| variRefL_(ChainableStack::memalloc_.alloc_array<vari*> | ||
| (A.rows() * (A.rows() + 1) / 2)) { | ||
| size_t pos = 0; | ||
| for (size_type i = 0; i < M_; ++i) { | ||
| for (size_type j = 0; j <= i; ++j) { | ||
| variRefA_[pos] = A.coeffRef(i, j).vi_; | ||
| variRefL_[pos] = new vari(L_A.coeffRef(i, j), false); | ||
| ++pos; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /* Reverse mode differentiation | ||
| * algorithm refernce: | ||
| * | ||
| * Mike Giles. An extended collection of matrix | ||
| * derivative results for forward and reverse mode AD. | ||
| * Jan. 2008. | ||
| * | ||
| * Note algorithm as laid out in Giles is | ||
| * row-major, so Eigen::Matrices are explicitly storage | ||
| * order RowMajor, whereas Eigen defaults to | ||
| * ColumnMajor. Also note algorithm | ||
| * starts by calculating the adjoint for | ||
| * A(M_ - 1, M_ - 1), hence pos on line 94 is decremented | ||
| * to start at pos = M_ * (M_ + 1) / 2. | ||
| * */ | ||
| virtual void chain() { | ||
| using Eigen::Matrix; | ||
| using Eigen::RowMajor; | ||
| Matrix<double, -1, -1, RowMajor> adjL(M_, M_); | ||
| Matrix<double, -1, -1, RowMajor> LA(M_, M_); | ||
| Matrix<double, -1, -1, RowMajor> adjA(M_, M_); | ||
| size_t pos = 0; | ||
| for (size_type i = 0; i < M_; ++i) { | ||
| for (size_type j = 0; j <= i; ++j) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer brackets around anythng bigger than a one-liner. |
||
| adjL.coeffRef(i, j) = variRefL_[pos]->adj_; | ||
| LA.coeffRef(i, j) = variRefL_[pos]->val_; | ||
| ++pos; | ||
| } | ||
| } | ||
|
|
||
| --pos; | ||
| for (int i = M_ - 1; i >= 0; --i) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A more idiomatic way to write this is
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The more idiomatic way is more confusing to me (personally)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bob-carpenter I'm going to leave this as is, the idiomatic way is confusing to me as well |
||
| for (int j = i; j >= 0; --j) { | ||
| if (i == j) { | ||
| adjA.coeffRef(i, j) = 0.5 * adjL.coeff(i, j) | ||
| / LA.coeff(i, j); | ||
| } else { | ||
| adjA.coeffRef(i, j) = adjL.coeff(i, j) | ||
| / LA.coeff(j, j); | ||
| adjL.coeffRef(j, j) -= adjL.coeff(i, j) | ||
| * LA.coeff(i, j) / LA.coeff(j, j); | ||
| } | ||
| for (int k = j - 1; k >=0; --k) { | ||
| adjL.coeffRef(i, k) -= adjA.coeff(i, j) | ||
| * LA.coeff(j, k); | ||
| adjL.coeffRef(j, k) -= adjA.coeff(i, j) | ||
| * LA.coeff(i, k); | ||
| } | ||
| variRefA_[pos--]->adj_ += adjA.coeffRef(i, j); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| /* Reverse mode specialization of | ||
| * cholesky decomposition | ||
| * | ||
| * Internally calls llt rather than using | ||
| * stan::math::cholesky_decompose in order | ||
| * to use selfadjointView<Lower> optimization. | ||
| * | ||
| * Note chainable stack varis are created | ||
| * below in Matrix<var, -1, -1> | ||
| * | ||
| * @param Matrix A | ||
| * @return L cholesky factor of A | ||
| */ | ||
| Eigen::Matrix<var, -1, -1> | ||
| cholesky_decompose(const Eigen::Matrix<var, -1, -1> &A) { | ||
| stan::math::check_square("cholesky_decompose", "A", A); | ||
| stan::math::check_symmetric("cholesky_decompose", "A", A); | ||
|
|
||
| Eigen::Matrix<double, -1, -1> L_A(value_of_rec(A)); | ||
| Eigen::LLT<Eigen::MatrixXd> L_factor | ||
| = L_A.selfadjointView<Eigen::Lower>().llt(); | ||
| check_pos_definite("cholesky_decompose", "m", L_factor); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't you avoid this check_pos_definite and just look at hte result of the factorization? This test is super expensive.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This actually avoids the cost of
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for all the clarifications. I didn't catch I've always seen those countdown loops done the way I
|
||
| L_A = L_factor.matrixL(); | ||
|
|
||
| // NOTE: this is not a memory leak, this vari is used in the | ||
| // expression graph to evaluate the adjoint, but is not needed | ||
| // for the returned matrix. Memory will be cleaned up with the | ||
| // arena allocator. | ||
| cholesky_decompose_v_vari *baseVari | ||
| = new cholesky_decompose_v_vari(A, L_A); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why create a whole vari if you're not going to use it?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the pattern for matrix derivatives. This vari is the one which actually has |
||
| stan::math::vari dummy(0.0, false); | ||
| Eigen::Matrix<var, -1, -1> L(A.rows(), A.cols()); | ||
| size_t pos = 0; | ||
| for (size_type i = 0; i < L.cols(); ++i) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is backward for memory locality. Is there a way to reorganize? Memory locality is huge in terms of efficiency issues.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is one of the matrices that should be row-major; good catch, I'll change the storage order.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now I'm not sure if this is a good idea. I don't know Eigen's internals well enough to determine what that'll do to computations done on L. What's the impact of multiplying a row-major storage order matrix by a column major storage order matrix? I'll run some tests to determine if there's any slowdown.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's all about memory locality. What Eigen will do
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bob-carpenter I took care of this in the latest version that's testing right now. The changes are to lines 149 through 161 and in the constructor. I'm no longer iterating through any column-major matrix in the wrong order, and any row-major matrices are explicitly declared as such. However, it makes the code a little harder to read because instead of just updating the position in the array of pointers variRefL_ like ++pos, I'm doing the mapping from (i, j) to position in a row-major half-vector.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great! The reason to write code that works, get tests in place,
|
||
| for (size_type j = 0; j <= i; ++j) | ||
| L.coeffRef(i, j).vi_ = baseVari->variRefL_[pos++]; | ||
| for (size_type k = (i + 1); k < L.cols(); ++k) | ||
| L.coeffRef(i, k).vi_ = &dummy; | ||
| } | ||
| return L; | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
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 variables should be private if you're being picky.