#This script does a Riemanniean Manifold Hamiltonian MCMC leapfrog proposal for the LOLOG model:
#simply updates the covariance matrix at each step compared to the regular HMC algorithm
#uses simulations to estimate the Fisher information matrix, rather than just using the observed information
#updates the momentum matrix at each step when the positions is updated
#note that this means the HMC dynamics are wrong - so should only be used for a small number of steps e.g. 1
#Does not do fixed point iteration.
#' @export
RMHMC_sim_proposal <- function(theta_0, #the current paramter value - starting position for HMC
net, #the observed network,
s, #the fixed edge permutation
formula_rhs, #rhs formula of model
prior = function(theta,net,change_on){(sum(abs(theta)<10) == length(theta))*((1/20)**length(theta))}, #prior function for theta
prior_grad = NULL, #specify prior derivative to get speed up
L_steps, #the number of leapfrog steps
epsilon = NULL, #size of the leapfrog step
epsilon_factor = 1, #used when epsilon is null to amend the generate "ideal epsilon"
momentum_sigma, #sigma used in momentum function generator
change_off = NULL,
change_on = NULL,
proposal_sims = 40,#number of sims used for the calculation of Hessian
...
){
#Rename the theta_0 as q - in line with HMC literature
q <- theta_0
names(q) <- NULL
#Make the lolog formula
formula <- as.formula(paste("net ~",formula_rhs,sep = ""))
#calculate the change stats for the given permutaion and graph
if(is.null(change_on)){
change_on <- lolog_change_stats(net,s,formula_rhs)
}
#From s get the permutation heads and tails:
n <- network.size(net)
edges <- combn(seq(1,n),2)
tails <- edges[1,][s]-1
heads <- edges[2,][s]-1
#Calculate gradient of the log prior + log liklihood function for LOLOG
q_grad <- function(q,change_on=NULL,network=NULL){
if(is.null(network)){
network <- net
}
if((is.null(change_on))){
change_on <- lolog_change_stats(network,s,formula_rhs)
}
if(is.null(prior_grad)){
prior_deriv <- (numDeriv::grad(prior,q,net=net,change_on = change_on)/prior(q,net=net,change_on = change_on))
}
else{prior_deriv <- prior_grad(q,net=net,change_on = change_on)/prior(q,net=net,change_on = change_on)}
if(sum(is.na(prior_deriv)) != 0){
prior_deriv <- rep(0,length(q))
}
#derivative of change statistics on top
#is just graph statistics
top_deriv <- lolog::calculateStatistics(as.formula(paste("network ~ ",formula_rhs)))
bottom_deriv <- bottom_deriv_helper_cpp(change_on = change_on,q = q,weights = NULL)
#return their sum with the correct signs
return(-prior_deriv - top_deriv + bottom_deriv)
}
#specify a local momentum matrix if no momentum is supplied:
#it is the local covariance matrix at the starting point
#"ideal" dynamics under the assumption that the proposal distribution is Gaussian are sampling the momentum from the inverse covariance matrix
momentum_assign <- function(q){
t<-proc.time()
model <- lolog::createLatentOrderLikelihood(formula,theta = q)
momentum_sigma <- lapply(1:proposal_sims,function(i){
tmp = model$generateNetworkWithEdgeOrder(heads,tails)
grad = tryCatch({q_grad(q,change_on = tmp$changeStats, network = lolog::as.network(tmp$network))},error = function(e){return(rep(0,length(q)))})
#grad = q_grad(q,change_on = tmp$changeStats, network = lolog::as.network(tmp$network))
return(outer(grad,grad))
})
rm(list = "model")
momentum_sigma <- Reduce("+",momentum_sigma)/length(momentum_sigma)
#check if singular, if not add to singular values
if(abs(det(momentum_sigma)) < 10**(-6)){
for(i in 10**seq(-6,20,1)){
if(abs(det(momentum_sigma))>10**(-6)){
break
}
else{
tmp = svd(momentum_sigma)
values = tmp$d
values[which(values == min(values))] <- values[which(values == min(values))] + i
momentum_sigma <- tmp$u%*%diag(values)%*%t(tmp$v)
#momentum_sigma <- momentum_sigma + diag(min(diag(momentum_sigma))*i,dim(momentum_sigma)[1])
}
}
}
momentum_sigma_inv <- solve(momentum_sigma)
assign("momentum_sigma",momentum_sigma, envir = parent.env(environment()))
assign("momentum_sigma_inv",momentum_sigma_inv, envir = parent.env(environment()))
# print("Momentum is :")
# print(momentum_sigma)
# print("Momentum inversed is:")
# print(momentum_sigma_inv)
# print("Momentum Assigned")
#print(paste("assigning the momentum took", (proc.time() - t)[3], "seconds",sep=""))
return()
}
#debug(momentum_assign)
momentum_assign(q)
#If epsilon is null put it as the mimimum eigen value of the momentum matrix:
if(is.null(epsilon)){
epsilon <- (min(eigen(momentum_sigma)$values)**0.5)*epsilon_factor
}
#Generate initial momentum
p_init <- MASS::mvrnorm(1,rep(0,length(q)),Sigma = momentum_sigma)
p <- p_init
#Do half momentum update fist
p <- p - (epsilon/2)*q_grad(q,change_on = change_on)
#Do L_steps full updates
if(L_steps != 1){
for(i in 1:(L_steps-1)){
q <- q + epsilon*(momentum_sigma_inv%*%p)
if(prior(q)==0){
return(list(proposal = as.vector(q), prob_factor = 0))
}
p <- p - epsilon*q_grad(q,change_on = change_on)
#print(q)
#print(p)
#print(momentum_sigma)
#print(det(momentum_sigma))
#print(momentum_sigma_inv)
#print(det(momentum_sigma_inv))
#Update covariance matrix:
momentum_assign(q)
}
}
#Do final updates
q <- q + epsilon*(momentum_sigma_inv%*%p)
p <- p - (epsilon/2)*q_grad(q,change_on = change_on)
#negate the momentum variable - does not actually effect anything
p <- -p
#prob factor takes account of the joint distribution in the metropolis step
prob_factor = exp(0.5*t(p_init)%*%(momentum_sigma_inv%*%p_init) - 0.5*t(p)%*%(momentum_sigma_inv%*%p))
return(list(proposal = as.vector(q), prob_factor = prob_factor))
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.