Initialization error in serial dilution model

Howdy! I’d be grateful if someone would take a look at the below serial dilution model. I keep getting initialization errors…I guess I have looked at it for too long and can’t find anything… or maybe the model is just not very good.

This is a small part of a much larger and complicated model of a laboratory experiment involving several variations and different serial dilutions. I’m trying to get the simplest part to work first.

In this part, the concept is simple: you have a stock of spores (unknown volume and number of spores), where each 10ul sample from stock contains a latent number of spores lambda_mu. We can think of lambda_mu as a normal distribution with some standard deviation, that represent a population of 10ul samples from stock. Some samples from stock are taken and the goal is to find the number of spores in each sample, lambda_con. Each sample is serially diluted and then samples are put on growth plates and the number of spores are counted. The seral dilution has to be done, because the number of spores in the original sample is quite large with maybe mean 5e9. Some of the early serial dilutions have censored values because the spores on the plate are too numerous to count.
At every stage of dilution you get pipetting error. I am treating the error as coming from a single normal distribution with some mean and sd, and I scale the error appropriately at each step of the dilution.
Non-centered parameterization is used.

The data and model are below, which can be run and fully reproduce the problem (apologies for using an old rstan version with different array syntax, but that is all I have on this computer, and can’t currently update):

library(rstan)

#define data used in Stan model in a list
stan_data <- list(
N1_sp = 48,
s = c(330,330,49,48,23,23,0,0,330,330,51,74,11,10,0,8,330,330,57,49,8,2,1,0,330,330,34,39,6,2,0,0,330,330,49,49,14,2,1,2,330,330,47,52,2,5,0,1),
Ex = 6,
Ex_1 = rep(1:6, each=8),
censored_sp = rep(c(1,1,0,0,0,0,0,0), times=6),
U = 330,
dilution_plate_sp = rep(c(6,6,7,7,8,8,9,9), times=6)
)

#define Stan model
stan_model <- "
data {
  int N1_sp;  //number of observations (including censored values)
  int s[N1_sp];  //observed colony counts on plates
  int Ex;  //number of straight plate experiments
  int Ex_1[N1_sp];  //index for the straight plate experiments
  int censored_sp[N1_sp];  //indicator for censored observations
  int U;  //upper bound for censoring
  int dilution_plate_sp[N1_sp];  //the dilution plate indicator for the if else statements for straight plate serial dilutions
}
parameters {
  vector<lower=0>[Ex] lambda_con_std;
  real<lower=0> lambda_mu;
  real<lower=0> tau;
  vector[Ex] error_6_std;
  vector[Ex] error_7_std;
  vector[Ex] error_8_std;
  real<lower=0> tau_er;
}
transformed parameters {
  vector<lower=0>[Ex] lambda_con = lambda_mu + tau * lambda_con_std;  //the mean number of spores in the 10ul sample from stock (for straight plate data; same distribution parameters lambda_mu and tau as for the coupons)
  vector[Ex] error_6 = tau_er * error_6_std;
  vector[Ex] error_7 = tau_er * error_7_std;
  vector[Ex] error_8 = tau_er * error_8_std;
print(lambda_con);
}
model {
  // likelihood for straight plating data, including censoring
  for (n in 1:N1_sp) {

    if (dilution_plate_sp[n]==6) {
      if (censored_sp[n]==0) {
target += poisson_lpmf( s[n] | 0.000001 * lambda_con[Ex_1[n]] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.000001 * lambda_con[Ex_1[n]] );
      }
    }

    if (dilution_plate_sp[n]==7) {
      if (censored_sp[n]==0) {
target += poisson_lpmf( s[n] | 0.0000001 * lambda_con[Ex_1[n]] + 0.000001 * error_6[Ex_1[n]] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.0000001 * lambda_con[Ex_1[n]] + 0.000001 * error_6[Ex_1[n]] );
      }
    }

    if (dilution_plate_sp[n]==8) {
      if (censored_sp[n]==0) {
target += poisson_lpmf( s[n] | 0.00000001 * lambda_con[Ex_1[n]] + 0.0000001 * error_6[Ex_1[n]] + 0.0000001 * error_7[Ex_1[n]] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.00000001 * lambda_con[Ex_1[n]] + 0.0000001 * error_6[Ex_1[n]] + 0.0000001 * error_7[Ex_1[n]] );
      }
    }

    if (dilution_plate_sp[n]==9) {
      if (censored_sp[n]==0) {
target += poisson_lpmf( s[n] | 0.000000001 * lambda_con[Ex_1[n]] + 0.00000001 * error_6[Ex_1[n]] + 0.00000001 * error_7[Ex_1[n]] + 0.00000001 * error_8[Ex_1[n]] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.000000001 * lambda_con[Ex_1[n]] + 0.00000001 * error_6[Ex_1[n]] + 0.00000001 * error_7[Ex_1[n]] + 0.00000001 * error_8[Ex_1[n]] );
      }
    }

  }
  //priors
  target += normal_lpdf(lambda_mu | 5e9, 1e9);
  target += normal_lpdf(tau | 1.5e9, 5e8) - 1 * normal_lccdf(0 | 1.5e9, 5e8);
  target += normal_lpdf(tau_er |  0, 7.5e7) - 1 * normal_lccdf(0 |  0, 7.5e7);
  target += std_normal_lpdf(lambda_con_std);
  target += std_normal_lpdf(error_6_std);
  target += std_normal_lpdf(error_7_std);
  target += std_normal_lpdf(error_8_std);
}

generated quantities {
  vector[N1_sp] s_preds;
// predictions for straight plating
  for (n in 1:N1_sp) {
    if (dilution_plate_sp[n]==6) {
s_preds[n] = poisson_rng( 0.000001 * lambda_con[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==7) {
s_preds[n] = poisson_rng( 0.0000001 * lambda_con[Ex_1[n]] + 0.000001 * error_6[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==8) {
s_preds[n] = poisson_rng( 0.00000001 * lambda_con[Ex_1[n]] + 0.0000001 * error_6[Ex_1[n]] + 0.0000001 * error_7[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==9) {
s_preds[n] = poisson_rng( 0.000000001 * lambda_con[Ex_1[n]] + 0.00000001 * error_6[Ex_1[n]] + 0.00000001 * error_7[Ex_1[n]] + 0.00000001 * error_8[Ex_1[n]] );
    }
  }
}
"



#fit Stan model
un20_sp <- stan(model_code = stan_model, data = stan_data,
             chains = 1, iter = 10, init="0",
             thin = 1, cores = 4)

Perhaps it’s too flexible with all the error terms? Priors reflect the manufacturer specs for accuracy error from the pipette.

Thanks!

1 Like

So it seems to initialize if I supply initial values for lambda_mu, and if I run 4 chains, one of them will run:

init_list <- list(list(lambda_mu = 5e9, lambda_mu = 5.1e9, lambda_mu = 4.9e9, lambda_mu = 5.01e9))

#fit Stan model
un20_sp <- stan(model_code = stan_model, data = stan_data,
             chains = 4, iter = 2000, init=init_list,
             thin = 1, cores = 4, control=list(adapt_delta=0.99))

But 3 chains don’t.
I also tightened up the priors on the error parameters. I’m thinking maybe I just don’t have enough data for all these pipetting errors.

I get really suspicious that something is poorly specified when I have to give initial values.

Any time I see 0.000000001 * lambda_con[Ex_1[n]] or similar, my first suggestion is to try doing all of this computation on the log scale (using log_sum_exp for the sum). Stan even has a log paramterization of the poisson, though I’m not sure if Stan yet has the log parameterization of the poisson lccdf.

Just an efficiency concern, but you can store all of the intermediate terms in these sums and build them recursively in a small fraction of the total computation you’re doing here.

1 Like

Good point. I tried a simple Poisson model with something like 0.000000001 * lambda and it worked fine, even without supplying initial values. Are you suggesting something like the following?

    if (dilution_plate_sp[n]==6) {
      if (censored_sp[n]==0) {
target += poisson_log_lpmf( s[n] | log(0.000001 * lambda_con[Ex_1[n]]) );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.000001 * lambda_con[Ex_1[n]] );
      }
    }

    if (dilution_plate_sp[n]==7) {
      if (censored_sp[n]==0) {
target += poisson_log_lpmf( s[n] | log_sum_exp(log(0.0000001 * lambda_con[Ex_1[n]]), log(0.000001 * error_6[Ex_1[n]])) );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.0000001 * lambda_con[Ex_1[n]] + 0.000001 * error_6[Ex_1[n]] );
      }
    }
//.....

Originally I was going to put everything, lambda_mu and all, on the log scale, because I always try to use poisson_log_lpmf but it seemed less straightforward with the serial dilutions and having to multiply the lambda’s and error’s by the dilution factors (which would need adjusting for dilutions of the natural log of lambda).

No not yet.

I have a feeling there is a lot in the model (especially the full one) that could be done more efficiently! At first I was aiming for being explicit rather than efficient. How do you mean?

Rewrite log(0.000001 * lambda_con[Ex_1[n]]) as log(0.000001) + log(lambda_con[Ex_1[n]])

Hrm. I though that there should be a simple recursion here, but that’s not what you’ve implemented in your Stan code. Maybe I’m missing something, but are you sure all those 0.000 ... 1s in your code have the right numbers of zeros in them?

In particular, I would have said that given volumetric pipetting error \epsilon expressed in units of the volume being pipetted, and neglecting the pipetting error in the 9-parts-water fraction, then a single 10X dilution from initial concentration C should yield a final concentration \frac{C + C\epsilon}{10 + \epsilon}, which might be reasonably approximated as \frac{C + C\epsilon}{10}, which is what I thought (think?) you were driving at with the Stan model. We can compute the different terms recursively, and re-use them in both the pmf and lccdf lines, like

c1 = 0.1 * (C + C*epsilon1)
c2 = 0.1 * (c1 + c1*epsilon2)
...

Which is way more efficient than writing
c2 = 0.1 * (0.1 * (C + C*epsilon1) + epsilon2 * 0.1 * (C + C*epsilon1))

1 Like

How do I used log_sum_exp for more than two inputs? something like log_sum_exp( log(0.00000001) + log(lambda_con[Ex_1[n]]), log(0.0000001) + log(error_6[Ex_1[n]]), log(0.0000001) + log(error_7[Ex_1[n]])) throws an error. I guess I am missing something obvious.

I think I have implemented your suggestions, but I can’t get it to sample, even with initial values, now. Maybe I have something amiss.

stan_model <- "
data {
  int N1_sp;  //number of observations (including censored values)
  int s[N1_sp];  //observed colony counts on plates
  int Ex;  //number of straight plate experiments
  int Ex_1[N1_sp];  //index for the straight plate experiments
  int censored_sp[N1_sp];  //indicator for censored observations of straight plate
  int U;  //upper bound for censoring
  int dilution_plate_sp[N1_sp];  //the dilution plate indicator for the if else statements for straight plate serial dilutions
}
parameters {
  vector<lower=0>[Ex] lambda_con_std;
  real<lower=0> lambda_mu;
  real<lower=0> tau;
  vector[Ex] error_6_std;
  vector[Ex] error_7_std;
  vector[Ex] error_8_std;
  real<lower=0> tau_er;
}
transformed parameters {
  vector<lower=0>[Ex] lambda_con = lambda_mu + tau * lambda_con_std;  //the mean number of spores in the 10ul sample from stock (for straight plate data; same distribution parameters lambda_mu and tau as for the coupons)
  vector[Ex] error_6 = tau_er * error_6_std;
  vector[Ex] error_7 = tau_er * error_7_std;
  vector[Ex] error_8 = tau_er * error_8_std;
print(lambda_con);
}

model {

// likelihood for straight plating data, including censoring
vector[N1_sp] c1;
vector[N1_sp] c2;
vector[N1_sp] c3;
vector[N1_sp] c4;

  for (n in 1:N1_sp) {

c1[n] = log(0.000001) + log(lambda_con[Ex_1[n]]);
c2[n] = log_sum_exp( log(0.1) + log(c1[n]), log(0.000001) + log(error_6[Ex_1[n]]) );
c3[n] = log_sum_exp( log(0.1) + log(c2[n]), log(0.0000001) + log(error_7[Ex_1[n]]) );
c4[n] = log_sum_exp( log(0.1) + log(c3[n]), log(0.00000001) + log(error_8[Ex_1[n]]) );

    if (dilution_plate_sp[n]==6) {
      if (censored_sp[n]==0) {
target += poisson_log_lpmf( s[n] | c1[n] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.000001 * lambda_con[Ex_1[n]] );
      }
    }

    if (dilution_plate_sp[n]==7) {
      if (censored_sp[n]==0) {
target += poisson_log_lpmf( s[n] | c2[n] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.0000001 * lambda_con[Ex_1[n]] + 0.000001 * error_6[Ex_1[n]] );
      }
    }

    if (dilution_plate_sp[n]==8) {
      if (censored_sp[n]==0) {
target += poisson_log_lpmf( s[n] | c3[n] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.00000001 * lambda_con[Ex_1[n]] + 0.0000001 * error_6[Ex_1[n]] + 0.0000001 * error_7[Ex_1[n]] );
      }
    }

    if (dilution_plate_sp[n]==9) {
      if (censored_sp[n]==0) {
target += poisson_log_lpmf( s[n] | c4[n] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.000000001 * lambda_con[Ex_1[n]] + 0.00000001 * error_6[Ex_1[n]] + 0.00000001 * error_7[Ex_1[n]] + 0.00000001 * error_8[Ex_1[n]] );
      }
    }

  }


//priors
  target += normal_lpdf(lambda_mu | 5e8, 1e8);
  target += normal_lpdf(tau | 1.5e8, 5e7) - 1 * normal_lccdf(0 | 1.5e8, 5e7);
  target += normal_lpdf(tau_er | 0, 7.5e6) - 1 * normal_lccdf(0 | 0, 7.5e6);
  target += std_normal_lpdf(lambda_con_std);
  target += std_normal_lpdf(error_6_std);
  target += std_normal_lpdf(error_7_std);
  target += std_normal_lpdf(error_8_std);
}

generated quantities {
  vector[N1_sp] s_preds;

// predictions for straight plating
  for (n in 1:N1_sp) {
    if (dilution_plate_sp[n]==6) {
s_preds[n] = poisson_rng( 0.000001 * lambda_con[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==7) {
s_preds[n] = poisson_rng( 0.0000001 * lambda_con[Ex_1[n]] + 0.000001 * error_6[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==8) {
s_preds[n] = poisson_rng( 0.00000001 * lambda_con[Ex_1[n]] + 0.0000001 * error_6[Ex_1[n]] + 0.0000001 * error_7[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==9) {
s_preds[n] = poisson_rng( 0.000000001 * lambda_con[Ex_1[n]] + 0.00000001 * error_6[Ex_1[n]] + 0.00000001 * error_7[Ex_1[n]] + 0.00000001 * error_8[Ex_1[n]] );
    }
  }
}
"

What happens if you update everything in the lccdf calls to also use those precomputed quantities, or rather the exponentials of those quantities?

And just for efficiency, note that since you are computing c1, c2, etc inside the loop you don’t actually have to make them vectors over [n]. You can just let them be reals and recompute them for the current value of n on each pass through the loop.

Unfortunately it doesn’t work with or without initial values.

data {
  int N1_sp;  //number of observations (including censored values)
  int s[N1_sp];  //observed colony counts on plates
  int Ex;  //number of straight plate experiments
  int Ex_1[N1_sp];  //index for the straight plate experiments
  int censored_sp[N1_sp];  //indicator for censored observations of straight plate
  int U;  //upper bound for censoring
  int dilution_plate_sp[N1_sp];  //the dilution plate indicator for the if else statements for straight plate serial dilutions
}
parameters {
  vector<lower=0>[Ex] lambda_con_std;
  real<lower=0> lambda_mu;
  real<lower=0> tau;
  vector[Ex] error_6_std;
  vector[Ex] error_7_std;
  vector[Ex] error_8_std;
  real<lower=0> tau_er;
}
transformed parameters {
  vector<lower=0>[Ex] lambda_con = lambda_mu + tau * lambda_con_std;  //the mean number of spores in the 10ul sample from stock (for straight plate data; same distribution parameters lambda_mu and tau as for the coupons)
  vector[Ex] error_6 = tau_er * error_6_std;
  vector[Ex] error_7 = tau_er * error_7_std;
  vector[Ex] error_8 = tau_er * error_8_std;
print(lambda_con);
}

model {

// likelihood for straight plating data, including censoring
real c1;
real c2;
real c3;
real c4;

  for (n in 1:N1_sp) {

c1 = log(0.000001) + log(lambda_con[Ex_1[n]]);
c2 = log_sum_exp( log(0.1) + log(c1), log(0.000001) + log(error_6[Ex_1[n]]) );
c3 = log_sum_exp( log(0.1) + log(c2), log(0.0000001) + log(error_7[Ex_1[n]]) );
c4 = log_sum_exp( log(0.1) + log(c3), log(0.00000001) + log(error_8[Ex_1[n]]) );

    if (dilution_plate_sp[n]==6) {
      if (censored_sp[n]==0) {
target += poisson_log_lpmf( s[n] | c1 );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | exp(c1) );
      }
    }

    if (dilution_plate_sp[n]==7) {
      if (censored_sp[n]==0) {
target += poisson_log_lpmf( s[n] | c2 );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | exp(c2) );
      }
    }

    if (dilution_plate_sp[n]==8) {
      if (censored_sp[n]==0) {
target += poisson_log_lpmf( s[n] | c3 );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | exp(c3) );
      }
    }

    if (dilution_plate_sp[n]==9) {
      if (censored_sp[n]==0) {
target += poisson_log_lpmf( s[n] | c4 );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | exp(c4) );
      }
    }

  }


//priors
  target += normal_lpdf(lambda_mu | 5e8, 1e8);
  target += normal_lpdf(tau | 1.5e8, 5e7) - 1 * normal_lccdf(0 | 1.5e8, 5e7);
  target += normal_lpdf(tau_er | 0, 7.5e6) - 1 * normal_lccdf(0 | 0, 7.5e6);
  target += std_normal_lpdf(lambda_con_std);
  target += std_normal_lpdf(error_6_std);
  target += std_normal_lpdf(error_7_std);
  target += std_normal_lpdf(error_8_std);
}

generated quantities {
  vector[N1_sp] s_preds;

// predictions for straight plating
  for (n in 1:N1_sp) {
    if (dilution_plate_sp[n]==6) {
s_preds[n] = poisson_rng( 0.000001 * lambda_con[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==7) {
s_preds[n] = poisson_rng( 0.0000001 * lambda_con[Ex_1[n]] + 0.000001 * error_6[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==8) {
s_preds[n] = poisson_rng( 0.00000001 * lambda_con[Ex_1[n]] + 0.0000001 * error_6[Ex_1[n]] + 0.0000001 * error_7[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==9) {
s_preds[n] = poisson_rng( 0.000000001 * lambda_con[Ex_1[n]] + 0.00000001 * error_6[Ex_1[n]] + 0.00000001 * error_7[Ex_1[n]] + 0.00000001 * error_8[Ex_1[n]] );
    }
  }
}

I’m wondering if there is a simple error in the code, because this seems worse than my original poisson_lpmf model, which, when supplied initial values, would at least sample well for 1 or 2 out of 4 chains.

Try debugging with print statements inside the loop over n to see if any of the concentrations or their exponentials is overflowing.

I couldn’t really figure out the problem with it, but after thinking about the experiment more, I don’t think all those error parameters are necessary anyway. And as I wondered in my OP, I think the model was just too flexible for the data at hand. The experiment takes 10ul samples from some unknown stock of spores and then serial dilutes down to an amount that can be put onto a plate and counted. Since the lab people are experienced, they don’t even bother plating each dilution until starting at the level shown in the Stan code above, which means that all the pipetting errors prior to that are just contained in the tau * lambda_con_std offsets. Plate counts are all less than 330, so by that time, for any dilutions that bring a count like 311 down to 31 and then 3, the pipetting error is small enough to be described by the variance in the Poisson distribution. For example, pipetting out 0.1 of a solution that contains 330 may result in a max error of + or - 3 counts.
The following code runs without any problems when provided reasonable initial values.

data {
  int N1_sp;  //number of observations (including censored values)
  int s[N1_sp];  //observed colony counts on plates
  int Ex;  //number of straight plate experiments
  int Ex_1[N1_sp];  //index for the straight plate experiments
  int censored_sp[N1_sp];  //indicator for censored observations of straight plate
  int U;  //upper bound for censoring
  int dilution_plate_sp[N1_sp];  //the dilution plate indicator for the if else statements for straight plate serial dilutions
}
parameters {
  vector<lower=0>[Ex] lambda_con_std;
  real<lower=0> lambda_mu;
  real<lower=0> tau;
}
transformed parameters {
  vector<lower=0>[Ex] lambda_con = lambda_mu + tau * lambda_con_std;  //the mean number of spores in the 10ul sample from stock (for straight plate data; same distribution parameters lambda_mu and tau as for the coupons)
}

model {

// likelihood for straight plating data, including censoring
  for (n in 1:N1_sp) {

    if (dilution_plate_sp[n]==6) {
      if (censored_sp[n]==0) {
target += poisson_lpmf( s[n] | 0.000001 * lambda_con[Ex_1[n]] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.000001 * lambda_con[Ex_1[n]] );
      }
    }

    if (dilution_plate_sp[n]==7) {
      if (censored_sp[n]==0) {
target += poisson_lpmf( s[n] | 0.0000001 * lambda_con[Ex_1[n]] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.0000001 * lambda_con[Ex_1[n]] );
      }
    }

    if (dilution_plate_sp[n]==8) {
      if (censored_sp[n]==0) {
target += poisson_lpmf( s[n] | 0.00000001 * lambda_con[Ex_1[n]] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.00000001 * lambda_con[Ex_1[n]] );
      }
    }

    if (dilution_plate_sp[n]==9) {
      if (censored_sp[n]==0) {
target += poisson_lpmf( s[n] | 0.000000001 * lambda_con[Ex_1[n]] );
      }
      else if (censored_sp[n]==1) {
target += poisson_lccdf(U | 0.000000001 * lambda_con[Ex_1[n]] );
      }
    }

  }

//priors
  target += normal_lpdf(lambda_mu | 5e8, 1e8);
  target += normal_lpdf(tau | 1.5e8, 5e7) - 1 * normal_lccdf(0 | 1.5e8, 5e7);
  target += std_normal_lpdf(lambda_con_std);
}

generated quantities {
  vector[N1_sp] s_preds;

// predictions for straight plating
  for (n in 1:N1_sp) {
    if (dilution_plate_sp[n]==6) {
s_preds[n] = poisson_rng( 0.000001 * lambda_con[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==7) {
s_preds[n] = poisson_rng( 0.0000001 * lambda_con[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==8) {
s_preds[n] = poisson_rng( 0.00000001 * lambda_con[Ex_1[n]] );
    }
    if (dilution_plate_sp[n]==9) {
s_preds[n] = poisson_rng( 0.000000001 * lambda_con[Ex_1[n]] );
    }
  }
}