Simulation and inference for a stochastic process with a random time of event in rstan

Hi! Thank you for your suggestions! I have implemented all three of them.
This is my new Stan model code:

// This Stan program defines a simple stochastic model of exponential decay where the
// initial time point tau is random.
// in this model, the index of the observation after tau is included as a parameter
// tau = index_after_tau_p * time_step - s
// and we have one set of parameters per possible value of index_after_tau_p

// non-centered parametrization

data {
  int<lower=0> M; // number of observed time points
  vector<lower=0>[M] y_obs; // observations
  vector<lower=0>[M] time_points; // time points of the observations
  real<lower=0> time_step; // time step between observations
  int<lower=2> max_index; // upper bound for index of the observation after tau
  int<lower=2, upper=max_index> min_index; // lower bound for index of the observation after tau
  // the index after tau cannot be one (i.e. that of the first observation)
}

transformed data{
  vector[M] log_y_obs;
  real log_unif; 
  int n_poss_ind;
  int n_random_states; 
  int x_start_index[max_index - min_index + 1];
  log_y_obs = log(y_obs); // we assume log-normally distributed noise and therefore log-transform the data
  n_poss_ind = max_index - min_index + 1; // number of possible values that the index after tau can take
  log_unif = - log(n_poss_ind); // we assume a uniform prior for the index after tau;
  n_random_states = (M - max_index + 1) * n_poss_ind + (n_poss_ind - 1) * 
                    n_poss_ind / 2; // the number of random process states = length of x_flat
  // calculate the index within x_flat where the values of the random process states 
  // start for the i-th possible value of index_after_tau
  // i = index_after_tau - min_index + 1
  for (i in 1:n_poss_ind) {
    x_start_index[i] = (i - 1) * (M - i - min_index + 3) + (i - 1) * (i - 2) / 2 + 1;
  }
}

parameters {
  vector<lower=0, upper=time_step>[n_poss_ind] s; // time step from tau (at which 
  // initial amount m0 is released) to the next observed time point  
  vector<lower=0>[n_poss_ind] m0; // initial amount released at time tau
  vector<lower=0>[n_poss_ind] theta; // decay rate
  vector<lower=0>[n_poss_ind] sigma; // measurement noise parameter
  vector[n_random_states] x_flat; // random process states
  simplex[n_poss_ind] index_after_tau_p; //index_after_tau_p[i] == P(index_after_tau = i + min_index - 1)
}

transformed parameters {
  vector[n_poss_ind] partial_lp;
  vector<lower=0>[n_poss_ind] x[M]; // all process states
  partial_lp = rep_vector(log_unif, n_poss_ind);
  for (i in 1:n_poss_ind) { // again: i = index_after_tau - min_index + 1
    //Compute the likelihood assuming a single given value of the index after tau
    for (t in 1:(i + min_index - 2)) { // before tau x[t] = 0
     x[t, i] = 0;
     // observation model
     partial_lp[i] += normal_lpdf(log_y_obs[t] | 0, sigma[i]);
    }
    
    // factor of the likelihood of the process for the time step from the state at 
    // time point tau to the state at the first observed time point
    partial_lp[i] +=  normal_lpdf(x_flat[x_start_index[i]] | 0, 1);
    x[i + min_index - 1, i] =  m0[i] - theta[i] * m0[i] * s[i] + 
                                sqrt(theta[i] * m0[i] * s[i]) * x_flat[x_start_index[i]];

    // observation model for the first observation after tau
    partial_lp[i] +=  normal_lpdf(log_y_obs[i + min_index - 1] | 
                                    log(x[i + min_index - 1, i] + 1), sigma[i]);
                                        
    for (t in (i + min_index):M) { // (index_after_tau + 1):M
      // factor of the likelihood of the process
      partial_lp[i] +=  normal_lpdf(x_flat[x_start_index[i] + t - i - min_index + 1] | 0, 1);
      x[t, i] = x[t-1, i] - theta[i] * x[t-1, i] * (time_points[t] - time_points[t-1]) +
                sqrt(theta[i] * x[t-1, i] * (time_points[t] - time_points[t-1])) *
                  x_flat[x_start_index[i] + t - i - min_index + 1];
      // observation model for the remaining observations after tau
      partial_lp[i] += normal_lpdf(log_y_obs[t] | log(x[t, i] + 1), sigma[i]);
    }
  }
}

model {
  //Weight the likelihood by the probability
  target += log_sum_exp(log(index_after_tau_p) + partial_lp);
  
  // priors
  theta ~ normal(0, 5);
  m0 ~ normal(300, 300);
  sigma ~ normal(0,1);
}

When I now run (still providing the correct value of the index after \tau via min_index and max_index)

y_obs <- read.table("y_obs.txt")[[1]]
init_values <- c(rep(list(list(x_flat=as.array(rep(1, times= 174)))), times = 4))
stanfit <- stan(file = "stanmodel_stoch_exp_decay_with_random_release_time_incl_index_with_sep_param_x_non_centered.stan", 
                data = list(M=181, time_points=seq(from=0, to=30, length.out=181), 
                            y_obs=y_obs, time_step=1/6, max_index=8, min_index=8), 
                seed = 42424,
                control = list(max_treedepth = 15),
                init = init_values)

there are no divergent transitions and the estimates look reasonable:

##             mean se_mean   sd   2.5%    50%  97.5% n_eff  Rhat
## m0[1]    201.798    0.03 2.31 197.71 201.68 206.61  4667 1.000
## theta[1]   0.111    0.00 0.01   0.10   0.11   0.13  1392 1.004
## sigma[1]   0.009    0.00 0.00   0.01   0.01   0.01   448 1.015
## s[1]       0.084    0.00 0.05   0.00   0.08   0.16  6319 1.000

(I’m initializing x_flat to ensure that the sampling starts in a region where the transformed parameters x are non-negative. I couldn’t find a better way to deal with the lower bound on x.)

Still, when I try to allow for several possible values for the index after \tau , e.g. max_index=9, min_index=7 , then this results in mostly divergent transitions and the chains looking messy:

y_obs <- read.table("y_obs.txt")[[1]]
M <- 181
max_index <- 9
min_index <- 7
length_x_flat <- (M - max_index + 1) * (max_index - min_index + 1) + 
              (max_index - min_index) * (max_index - min_index + 1) / 2
init_values <- c(rep(list(list(x_flat=as.array(rep(1, times = length_x_flat)))),
                     times = 4))
stanfit21 <- stan(file = "stanmodel_stoch_exp_decay_with_random_release_time_incl_index_with_sep_param_x_non_centered.stan",
                data = list(M=M, time_points=seq(from=0, to=30, length.out=181),
                            y_obs=y_obs, time_step=1/6, max_index=max_index, min_index=min_index),
                seed = 42424,
                control = list(max_treedepth = 15),
                init = init_values)

traceplot_tau_split_non_centered_param

I am pretty sure that there is no error in the indexing of x or x_flat, as I’ve looked at the values of x at different iterations after warm up and those look reasonable for those chains that sample from a region around the true value (chains 1,2, and 4 for index_after_tau=8, and chain 3 for index_after_tau=9):

But now I don’t know what else to look at. Do have another idea what could still be causing these problems?

1 Like