# QUESTION 1
# Slice sampling


rm(list = ls())

set.seed(5444)

# Given data
x1 <- 0.92
x2 <- 0.72

# x1 + x2
a <- x1 + x2

# Target:
# f(theta | x1, x2) proportional to
# theta^2 * exp(-(x1+x2)*theta), theta > 0
#
# The normalized target is Gamma(shape = 3, rate = 1.64)
# Number of retained samples
n.sample <- 1000

# Burn-in
burnin <- 200

# Total number of iterations
nMC <- n.sample + burnin

# Storage
theta.sample <- rep(NA, nMC)

# Initial value
theta <- 1



# Slice sampler
#
# Factor the target as
#
# g1(theta) = theta^2
# g2(theta) = exp(-a*theta)
#
# Introduce
# u1 | theta ~ Uniform(0, theta^2)
# u2 | theta ~ Uniform(0, exp(-a*theta))
#
# Conditional on u1 and u2:
#
# theta > sqrt(u1)
#
# and
#
# theta < -log(u2)/a


for (i in 1:nMC) {
  
  # Step 1: sample auxiliary variables
  
  u1 <- runif(
    1,
    min = 0,
    max = theta^2
  )
  
  u2 <- runif(
    1,
    min = 0,
    max = exp(-a * theta)
  )
  
  
  # Step 2: determine slice interval
  
  lower <- sqrt(u1)
  
  upper <- -log(u2) / a
  
  
  # Step 3: sample theta from the slice
  
  theta <- runif(
    1,
    min = lower,
    max = upper
  )
  
  theta.sample[i] <- theta
}


# Removing burn-in
theta.keep <- theta.sample[(burnin + 1):nMC]


# histogram


hist(
  theta.keep,
  breaks = 35,
  probability = TRUE,
  col = "grey",
  border = "white",
  main = "Slice Sampling",
  xlab = expression(theta)
)



#  theoretical density
#
# theta | x1,x2 ~ Gamma(shape=3, rate=1.64)


theta.grid <- seq(
  0,
  max(theta.keep) * 1.05,
  length.out = 500
)

f.theta <- dgamma(
  theta.grid,
  shape = 3,
  rate = a
)

lines(
  theta.grid,
  f.theta,
  lwd = 2
)

# numerical summaries
mean(theta.keep)
## [1] 1.723104
var(theta.keep)
## [1] 1.068989
range(theta.keep)
## [1] 0.1134067 6.4857268
# QUESTION 2
# Parallel Tempering


rm(list = ls())

set.seed(5444)



# Mixture distribution parameters


mu1 <- c(0, 0)
mu2 <- c(15, 0)
mu3 <- c(9, 1.5)

# Variances
# The covariance matrices are:
# 0.10*I, 0.08*I, and 0.09*I

sig1 <- 0.10
sig2 <- 0.08
sig3 <- 0.09

# Mixture weights

w1 <- 0.3
w2 <- 0.3
w3 <- 0.4


# Bivariate normal density
#
# If X ~ N(mu, sig*I),
#
# f(x) = 1/(2*pi*sig) *
#        exp{-||x-mu||^2/(2*sig)}
#
# Here sig is the variance, not the standard deviation.


dmvnorm.iso <- function(x, mu, sig) {
  
  f <- 1 / (2 * pi * sig) *
    exp(
      -sum((x - mu)^2) / (2 * sig)
    )
  
  return(f)
}



# Target mixture density
#
# f(x1,x2)
# =
# 0.3 N(mu1,0.10I)
# + 0.3 N(mu2,0.08I)
# + 0.4 N(mu3,0.09I)


poster <- function(x) {
  
  f1 <- w1 *
    dmvnorm.iso(
      x,
      mu = mu1,
      sig = sig1
    )
  
  f2 <- w2 *
    dmvnorm.iso(
      x,
      mu = mu2,
      sig = sig2
    )
  
  f3 <- w3 *
    dmvnorm.iso(
      x,
      mu = mu3,
      sig = sig3
    )
  
  f <- f1 + f2 + f3
  
  return(f)
}


# Log target density


log.poster <- function(x) {
  
  f <- poster(x)
  
  # Prevent taking log(0)
  f <- max(
    f,
    .Machine$double.xmin
  )
  
  return(
    log(f)
  )
}


# PARALLEL TEMPERING FUNCTION


parallel.tempering <- function(
    Tset,
    x0,
    alpha0,
    nMC,
    delta
) {
  
  # Number of temperature chains
  I <- length(Tset)
  
  # Dimension of x
  d <- ncol(x0)
  
  
  
  # Initial states
  
  
  x.current <- x0
  
  
  # Current log target densities
  logf.current <- rep(
    NA,
    I
  )
  
  
  for (k in 1:I) {
    
    logf.current[k] <-
      log.poster(
        x.current[k, ]
      )
  }
  
  
  
  # Storage for all chains
  
  
  sample.array <- array(
    NA,
    dim = c(
      I,
      d,
      nMC
    )
  )
  

  # Acceptance records

  
  MH.accept <- rep(
    0,
    I
  )
  
  MH.total <- rep(
    0,
    I
  )
  
  
  swap.accept <- rep(
    0,
    I - 1
  )
  
  swap.total <- rep(
    0,
    I - 1
  )
  
  
 
  # Main parallel tempering iterations
  
  for (m in 1:nMC) {
    
    
    # Draw u to determine whether to conduct
    # a parallel step or a swapping step
    
    u <- runif(1)
    
    
    
    # STEP 1: PARALLEL MCMC UPDATE
    #
    # With probability alpha0, update every chain.
   
    
    if (u < alpha0) {
      
      
      for (k in 1:I) {
        
        
        MH.total[k] <-
          MH.total[k] + 1
        
        
        
        # Random-walk proposal
       
        x.star <-
          x.current[k, ] +
          rnorm(
            d,
            mean = 0,
            sd = delta
          )
        
        
        # Log density at proposed value
        
        logf.star <-
          log.poster(
            x.star
          )
        
        
       
        # Tempered target:
        #
        # pi_k(x) proportional to pi(x)^(1/T_k)
        #
        # Therefore,
        #
        # log alpha =
        # [log pi(x*) - log pi(x)] / T_k
        
        log.alpha <-
          (
            logf.star -
              logf.current[k]
          ) /
          Tset[k]
        
        
       
        # Accept / reject
        
        if (
          log(runif(1)) <
          min(
            0,
            log.alpha
          )
        ) {
          
          
          x.current[k, ] <-
            x.star
          
          
          logf.current[k] <-
            logf.star
          
          
          MH.accept[k] <-
            MH.accept[k] + 1
        }
      }
      
      
      
      # STEP 2: SWAPPING STEP
      #
      # With probability 1-alpha0, choose neighboring chains
      # and propose a swap.
   
      
    } else {
      
      
      # Choose one neighboring pair
      
      j <- sample(
        1:(I - 1),
        size = 1
      )
      
      
      swap.total[j] <-
        swap.total[j] + 1
      
      
      
      # Swap acceptance probability
      #
      # log alpha_swap
      #
      # =
      #
      # (1/T_j - 1/T_{j+1})
      #
      # *
      #
      # [log pi(x_{j+1}) - log pi(x_j)]
     
      
      log.alpha.swap <-
        (
          1 / Tset[j] -
            1 / Tset[j + 1]
        ) *
        (
          logf.current[j + 1] -
            logf.current[j]
        )
      
      
     
      # Accept / reject swap
   
      
      if (
        log(runif(1)) <
        min(
          0,
          log.alpha.swap
        )
      ) {
        
        
        # Swap current states
        
        temp.x <-
          x.current[j, ]
        
        
        x.current[j, ] <-
          x.current[j + 1, ]
        
        
        x.current[j + 1, ] <-
          temp.x
        
        
        # Swap corresponding log densities
        
        temp.f <-
          logf.current[j]
        
        
        logf.current[j] <-
          logf.current[j + 1]
        
        
        logf.current[j + 1] <-
          temp.f
        
        
        swap.accept[j] <-
          swap.accept[j] + 1
      }
    }
    
    
   
    # Store the state of all chains
   
    
    sample.array[, , m] <-
      x.current
  }
  
  
 
  # Acceptance rates
  
  rMH <-
    MH.accept /
    MH.total
  
  
  rSW <-
    swap.accept /
    swap.total
  
  
  # Return results
  
  return(
    list(
      s.sample = sample.array,
      rMH = rMH,
      rSW = rSW
    )
  )
}


# RUN PARALLEL TEMPERING



# Temperatures


Tset <- c(
  100,
  3,
  1,
  0.5
)


I <- length(Tset)


# Initial states


x0 <- cbind(
  
  runif(
    I,
    min = -5,
    max = 15
  ),
  
  runif(
    I,
    min = -10,
    max = 10
  )
)


# Probability of conducting parallel step


alpha0 <- 0.5


# Number of MCMC iterations


nMC <- 80000


# Burn-in
burnin <- 10000


# Random-walk proposal standard deviation
delta <- 0.5



# Run algorithm


Mod <- parallel.tempering(
  Tset = Tset,
  x0 = x0,
  alpha0 = alpha0,
  nMC = nMC,
  delta = delta
)


# Extract samples

s.sample <-
  Mod$s.sample


# Print MH acceptance rates


Mod$rMH
## [1] 0.9252465 0.5642925 0.3568997 0.2369238
# Print swap acceptance rates


Mod$rSW
## [1] 0.06902219 0.49809003 0.65867408
# FUNCTION TO PLOT TEMPERED DENSITY + SAMPLE POINTS


plot.temperature <- function(
    temp,
    samples,
    main.title
) {
  
  
  # Grid
 
  
  x1.grid <- seq(
    -5,
    20,
    length.out = 160
  )
  
  
  x2.grid <- seq(
    -8,
    8,
    length.out = 130
  )
  
  
  den <- matrix(
    NA,
    nrow = length(x1.grid),
    ncol = length(x2.grid)
  )
  
  
  # Evaluate tempered density on grid

  
  for (i in 1:length(x1.grid)) {
    
    for (j in 1:length(x2.grid)) {
      
      
      xtemp <- c(
        x1.grid[i],
        x2.grid[j]
      )
      
      
      # Tempered target:
      #
      # pi_T(x) proportional to pi(x)^(1/T)
      
      den[i, j] <-
        exp(
          log.poster(xtemp) /
            temp
        )
    }
  }
  
  
  # Contour plot

  
  contour(
    x1.grid,
    x2.grid,
    den,
    nlevels = 12,
    drawlabels = FALSE,
    xlab = expression(x[1]),
    ylab = expression(x[2]),
    main = main.title,
    cex.main = 0.9
  )
  
  
  # Plot a thinned version of sample points

  
  use <- seq(
    1,
    nrow(samples),
    by = 20
  )
  
  
  points(
    samples[use, 1],
    samples[use, 2],
    pch = 20,
    cex = 0.35
  )
}



# PLOT TWO DIFFERENT TEMPERATURES
#
# T = 100: hot chain
# T = 1: original target distribution



par(
  mfrow = c(1, 2),
  mar = c(5, 4, 3, 1)
)


# Temperature T = 100


id.hot <- which(
  Tset == 100
)


sample.hot <- t(
  s.sample[
    id.hot,
    ,
    (burnin + 1):nMC
  ]
)


plot.temperature(
  temp = 100,
  samples = sample.hot,
  main.title = "T = 100"
)


# Temperature T = 1


id.target <- which(
  Tset == 1
)


sample.target <- t(
  s.sample[
    id.target,
    ,
    (burnin + 1):nMC
  ]
)


plot.temperature(
  temp = 1,
  samples = sample.target,
  main.title = "T = 1"
)

# Return plot layout to normal

par(
  mfrow = c(1, 1)
)


# QUESTION 3
# Stochastic volatility model


rm(list = ls())

set.seed(100)

# Parameters
T <- 100
phi <- 0.9
sig2 <- 1
sigma <- sqrt(sig2)
gamma <- 1

# Storage
x <- rep(NA, T)
y <- rep(NA, T)



# QUESTION 3(b)
# Simulate latent states and observations


# Initial state
x[1] <- rnorm(
  1,
  mean = 0,
  sd = sqrt(sig2 / (1 - phi^2))
)

# Latent process
for (t in 2:T) {
  
  x[t] <-
    phi * x[t - 1] +
    rnorm(
      1,
      mean = 0,
      sd = sigma
    )
}

# Observation process
# w_t ~ N(0, 0.25), so sd(w_t) = 0.5

w <- rnorm(
  T,
  mean = 0,
  sd = 0.5
)

y <- exp(gamma + x) * w


# FIGURE 3
# Latent state and observed process


par(
  mfrow = c(2, 1),
  mar = c(4, 4, 3, 1)
)

# Latent state
plot(
  1:T,
  x,
  type = "l",
  lwd = 2,
  xlab = "Time",
  ylab = expression(x[t]),
  main = "Latent State Process"
)

# Observed process
plot(
  1:T,
  y,
  type = "l",
  lwd = 1,
  xlab = "Time",
  ylab = expression(y[t]),
  main = "Observed Process"
)

points(
  1:T,
  y,
  pch = 16,
  cex = 0.4
)

par(mfrow = c(1, 1))


# QUESTION 3(c)
# Sequential Importance Resampling


N <- 1000

# Particle storage
Xs <- matrix(
  NA,
  nrow = N,
  ncol = T
)

# Posterior summaries
post.mean <- rep(NA, T)
post.lower <- rep(NA, T)
post.upper <- rep(NA, T)


# SIR algorithm


for (t in 1:T) {
  

  # Step 1: Propagate
 
  
  if (t == 1) {
    
    Xs[, t] <- rnorm(
      N,
      mean = 0,
      sd = sqrt(sig2 / (1 - phi^2))
    )
    
  } else {
    
    Xs[, t] <- rnorm(
      N,
      mean = phi * Xs[, t - 1],
      sd = sigma
    )
  }
  
  
  # Step 2: Importance weights

  
  # y_t | x_t ~ N(0, 0.25*exp(2*(gamma+x_t)))
  # Conditional standard deviation:
  # 0.5*exp(gamma+x_t)
  
  obs.sd <-
    0.5 *
    exp(
      gamma + Xs[, t]
    )
  
  logweight <- dnorm(
    y[t],
    mean = 0,
    sd = obs.sd,
    log = TRUE
  )
  
  # Stable normalization
  logweight <-
    logweight -
    max(logweight)
  
  weight <- exp(logweight)
  
  weight <-
    weight /
    sum(weight)
  
  
  # Step 3: Resample
  
  
  id <- sample(
    1:N,
    size = N,
    replace = TRUE,
    prob = weight
  )
  
  Xs[, t] <-
    Xs[
      id,
      t
    ]
  
  
  # Step 4: Posterior summaries
 
  
  post.mean[t] <-
    mean(
      Xs[, t]
    )
  
  post.lower[t] <-
    quantile(
      Xs[, t],
      probs = 0.025
    )
  
  post.upper[t] <-
    quantile(
      Xs[, t],
      probs = 0.975
    )
}

# Numerical summaries


range(x)
## [1] -4.142705  3.130338
range(y)
## [1] -66.44380  13.63763
sqrt(
  mean(
    (post.mean - x)^2
  )
)
## [1] 0.8027361
mean(
  x >= post.lower &
    x <= post.upper
)
## [1] 0.95
# QUESTION 3(d)
# FIGURE 4
# Filtering results


layout(
  matrix(
    c(1, 2, 3),
    ncol = 1
  ),
  heights = c(4, 1, 4)
)


# Panel 1: Latent state and filtering results


par(
  mar = c(4, 4, 3, 1)
)

ylim.state <- range(
  c(
    x,
    post.mean,
    post.lower,
    post.upper
  )
)

plot(
  1:T,
  x,
  type = "l",
  lwd = 2,
  lty = 1,
  ylim = ylim.state,
  xlab = "Time",
  ylab = "State",
  main = "Filtering Results for the Latent State"
)

# Posterior mean
lines(
  1:T,
  post.mean,
  lwd = 2,
  lty = 2
)

# Lower credible bound
lines(
  1:T,
  post.lower,
  lwd = 1,
  lty = 3
)

# Upper credible bound
lines(
  1:T,
  post.upper,
  lwd = 1,
  lty = 3
)


# Panel 2: Legend


par(
  mar = c(0, 0, 0, 0)
)

plot.new()

legend(
  "center",
  legend = c(
    "latent x",
    "posterior mean",
    "95% credible interval"
  ),
  lty = c(
    1,
    2,
    3
  ),
  lwd = c(
    2,
    2,
    1
  ),
  horiz = TRUE,
  bty = "n",
  cex = 0.9
)


# Panel 3: Observed data


par(
  mar = c(4, 4, 3, 1)
)

plot(
  1:T,
  y,
  type = "l",
  lwd = 1,
  xlab = "Time",
  ylab = "Observation",
  main = "Observed Data"
)

points(
  1:T,
  y,
  pch = 16,
  cex = 0.35
)

# Reset layout
layout(matrix(1))
par(mfrow = c(1, 1))