syntax as in this thread:
https://groups.google.com/d/topic/stan-users/HcvYaDu71_Y/discussion
The reason is that the multivariate normal or a
multivariate t distributions in Stan are currently only parameterized in
terms of the covariance matrix. So, the C++ code that implements these
two distributions inverts the covariance matrix (it actually takes a
Cholesky decomposition and inverts the Cholesky factor) which is costly
for large matrices and generates a huge expression tree to keep track of
the auto-differentiation.
So, if you can do something clever to
invert the covariance matrix, you can go faster with less RAM used. In
the link above, we had the case where the covariance matrix was a scalar
variance parameter times a known matrix. For a factor structure like
Sigma <- Lambda * Phi * Lambda' + Theta;
with a diagonal Theta, you could use the Woodbury Identity
https://en.wikipedia.org/wiki/Woodbury_matrix_identity
to get something like
for(k in 1:K) {
for(j in 1:K) {
Theta_inverse[j,k] = 0.0;
}
Theta_inverse[k,k] <- 1/Theta[k,k];
}
Sigma_inverse <- Theta_inverse - Theta_inverse * Lambda *
inverse(inverse(Phi) + Lambda' * Theta_inverse * Lambda) *
Lambda' * Theta_inverse;
(and in your case Phi is the identity matrix so you don't actually need to do inverse(Phi))
In which case, you could do some of this in the .stan file
lp__ <- lp__ - 0.5 * (x - mu)' * Sigma_inverse * (x - mu);
But if that is all you need Sigma_inverse for, then you could consolidate without explicitly forming Sigma_inverse
model {
vector[K] x_mu_ThetaInv;
real sum_of_squares;
for(k in 1:K) {
x_mu_ThetaInv[k] <- (x - mu) / Theta[k,k];
}
// (x - mu)' * Sigma_inverse * (x - mu)
sum_of_squares <- dot_product(x_mu_ThetaInv,x_mu_ThetaInv) -
x_mu_ThetaInv' * Lambda * inverse(inverse(Phi) + Lambda' * Theta_inverse * Lambda) *
Lambda' * x_mu_ThetaInv;
lp__ <- lp__ - 0.5 * sum_of_squares;
But,
you need to remember the (log) determinant part of the multivariate
normal (or t). You could do log(determinant(Sigma)) or
log(1/determinant(Sigma_inverse)) but the determinant() function is a
computational hazard because it involves a QR decomposition of a general
parameterized matrix and thus a giant expression tree of auto-diff
stuff. So, instead it is a bit better to calculate the determinant
yourself using a Cholesky decomposition to exploit the positive
definiteness of Sigma, as in
L <- cholesky_decompose(Sigma);
// det(Sigma) == det(L)^2 so 0.5 * log(det(Sigma)) == sum(log(diagonal(L)))
lp__ <- lp__ - sum(log(diagonal(L)));
That
would probably help a good bit, but the inverse() function also does
not exploit positive definiteness. So, you could gain a little more
speed by doing the inversion via a Cholesky factorization. Except it
looks like we don't actually expose the triangular solver to the Stan
parser for some reason, so never mind.
For a covariance matrix with AR1 structure, equation 5.2.15 in Hamilton's textbook
http://books.google.com/books?id=B8_1UBmqVUoC&lpg=PP1&ots=2oJHZoc1xP&dq=Hamilton%20Time&pg=PA120#v=onepage&q=%22could%20be%20written%20down%20immediately%22&f=false
implies something like
parameters {
real phi<lower=-1,upper=1>;
real sigma_squared<lower=0>;
vector[K] mu;
}
transformed parameters {
real denominator;
cov_matrix[K] Omega;
denominator <- 1 - square(phi);
for(i in 1:(K - 1)) {
Omega[i,i] <- sigma_squared / denominator;
for(j in (i+1):K) {
Omega[i,j] <- sigma_squared * pow(rho, abs(j - i)) / denominator;
Omega[j,i] <- Omega[i,j];
}
}
Omega[K,K] <- sigma_squared / denominator;
}
model {
x ~ multi_normal(mu, V); // entails slow inversion of V
}
But
Equation 5.2.18 in Hamilton also implies that a faster way to get (x -
mu)' * inverse(V) * (x - mu) is to use the Cholesky decomposition where
V^-1 = L' * L
Omega^-1 = 1/sigma_squared * V^-1
and L is lower triangular. So, we get
parameters {
real phi<lower=-1,upper=1>;
real sigma_squared<lower=0>;
vector[K] mu;
}
model {
vector[K] x_mu;
real sum_of_squares;
x_mu <- x - mu;
// post-multiply L by x_mu without explicitly forming L, square, and sum
sum_of_squares <- (1 - square(phi)) * square(x_mu[1]); // (row(L,1) * x_mu)^2
for(k in 2:K) {
sum_of_squares <- sum_of_squares + square(-phi * x_mu[k - 1] + x_mu[k]);
}
sum_of_squares <- sum_of_squares / sigma_squared;
lp__ <- lp__ - 0.5 * sum_of_squares; // kernel of multivariate normal
// add 0.5 * log(det(1/sigma_squared * V^-1)) in the form of:
lp__ <- lp__ + 0.5 * log( 1 - square(phi) ) + 0.5 * K * log(1 / sigma_squared);
}
Hopefully,
that is sufficiently clear without too many typos. This will get easier
for the user once we get around to implementing a covariance_matrix
class in C++.
Ben